Astro Cheat Sheet
A reference for Astro's component syntax, islands architecture, client directives, and content collections for content-focused websites.
Astro Component
Frontmatter runs at build/request time; the template renders below it.
---import Layout from '../layouts/Layout.astro';const { title } = Astro.props;const posts = await fetch('https://api.example.com/posts').then((r) => r.json());---<Layout title={title}> <h1>{title}</h1> <ul> {posts.map((post) => <li>{post.title}</li>)} </ul></Layout><style> h1 { color: darkslateblue; }</style>
Content Collections
Type-safe, schema-validated Markdown/MDX content.
// src/content/config.tsimport { defineCollection, z } from 'astro:content';const blog = defineCollection({ type: 'content', schema: z.object({ title: z.string(), pubDate: z.date(), draft: z.boolean().default(false), }),});export const collections = { blog };// usage in a pageimport { getCollection } from 'astro:content';const posts = await getCollection('blog', ({ data }) => !data.draft);
Islands Architecture
How Astro controls which components ship JavaScript to the client.
- Islands architecture- Astro ships zero JS by default; only components with a client directive get hydrated
- client:load- hydrates the component immediately when the page loads
- client:idle- hydrates the component when the browser is idle (requestIdleCallback)
- client:visible- hydrates the component once it scrolls into the viewport
- client:only- e.g. client:only='react' skips server rendering and renders only on the client
- .astro components- support top-level await and can mix multiple UI frameworks in one project
- getStaticPaths- defines the dynamic route params for statically generated pages
API Routes & Config
A server endpoint and the project configuration file.
// src/pages/api/hello.tsexport async function GET() { return new Response(JSON.stringify({ message: 'Hello' }), { headers: { 'Content-Type': 'application/json' }, });}// astro.config.mjsimport { defineConfig } from 'astro/config';import react from '@astrojs/react';export default defineConfig({ integrations: [react()], output: 'static', // or 'server' for SSR});
Middleware
Intercept every request/render to inject auth, redirects, or shared locals before a page renders.
// src/middleware.tsimport { defineMiddleware, sequence } from 'astro:middleware';const auth = defineMiddleware(async (context, next) => { const token = context.cookies.get('session')?.value; context.locals.user = token ? await getUser(token) : null; if (!context.locals.user && context.url.pathname.startsWith('/admin')) { return context.redirect('/login'); } return next();});const logging = defineMiddleware(async (context, next) => { const start = Date.now(); const response = await next(); console.log(`${context.url.pathname} - ${Date.now() - start}ms`); return response;});export const onRequest = sequence(auth, logging);
View Transitions
Enable SPA-like animated navigation between pages using the native View Transitions API.
---// src/layouts/Layout.astroimport { ClientRouter } from 'astro:transitions';---<html> <head> <ClientRouter /> </head> <body> <header transition:persist> <!-- persists across navigations instead of remounting --> </header> <main transition:animate="slide"> <slot /> </main> </body></html>
astro:assets Image Optimization
Import local images for automatic width/height inference, format conversion, and lazy loading.
---import { Image, getImage } from 'astro:assets';import hero from '../assets/hero.jpg';const optimized = await getImage({ src: hero, format: 'avif', width: 800 });---<Image src={hero} alt="Hero banner" widths={[400, 800, 1200]} sizes="(max-width: 800px) 100vw, 800px" loading="eager" /><img src={optimized.src} width={optimized.attributes.width} height={optimized.attributes.height} alt="Precomputed variant" />
Output Modes & Adapters
How Astro decides what gets prerendered versus rendered on demand.
- output: 'static'- default; every route is prerendered to HTML at build time
- output: 'server'- every route renders on demand via an adapter (node, vercel, netlify, cloudflare)
- export const prerender = true- opts a single route into static generation while output is 'server' (hybrid rendering)
- export const prerender = false- opts a single route into on-demand rendering while output is 'static'
- Adapters- @astrojs/node, @astrojs/vercel, @astrojs/cloudflare translate SSR output to a deploy target's runtime
- Astro.locals- typed per-request storage set by middleware and read in pages/endpoints
- getStaticPaths + prerender- combine to statically render only a subset of dynamic routes, deferring the rest
Content Collection References
Link entries across collections with reference() and render Markdown/MDX bodies with render().
// src/content/config.tsimport { defineCollection, reference, z } from 'astro:content';const blog = defineCollection({ type: 'content', schema: z.object({ title: z.string(), author: reference('authors'), relatedPosts: z.array(reference('blog')).optional(), }),});export const collections = { blog, authors: defineCollection({ type: 'data', schema: z.object({ name: z.string() }) }) };// src/pages/blog/[slug].astroimport { getEntry, render } from 'astro:content';const post = await getEntry('blog', Astro.params.slug);const { Content } = await render(post);
Add a client:* directive only to the specific interactive component (e.g. a like button), not the whole page -- that is the entire point of islands architecture, and it is what keeps Astro sites shipping near-zero JavaScript by default.