Astro
Astro cheat sheet with islands architecture, component syntax, content collections, routing, and SSG/SSR patterns with code examples.
Sign in to mark items as known and track your progress.
Sign inGetting Started
Project setup and basic configuration
Create New Project
# Interactive CLI setup
npm create astro@latest
# With specific template
npm create astro@latest -- --template blog
# DETAILED_TAB:
# Create and start project
npm create astro@latest my-site
cd my-site
npm install
npm run dev
# Available templates:
# minimal, blog, portfolio, docsProject Structure
my-astro-site/
├── src/
│ ├── components/ # Reusable components (.astro, .jsx, .vue)
│ ├── layouts/ # Page layouts with <slot />
│ ├── pages/ # File-based routing
│ ├── content/ # Content collections (Markdown/MDX)
│ └── assets/ # Images, fonts processed by Astro
├── public/ # Static assets (copied as-is)
└── astro.config.mjs # Astro configurationComponents & Syntax
Astro component structure, props, and slots
Component Structure
---
// Component Script (server-side only)
import Header from './Header.astro';
const name = "World";
// Fetch data at build time
const response = await fetch('https://api.example.com/data');
const data = await response.json();
---
<!-- Component Template -->
<Header />
<h1>Hello {name}!</h1>
<p>{data.message}</p>
<style>
h1 { color: blue; }
</style>Props & TypeScript
---
interface Props {
title: string;
count?: number;
items: string[];
}
const { title, count = 0, items } = Astro.props;
---
<div>
<h2>{title}</h2>
<p>Count: {count}</p>
<ul>
{items.map(item => <li>{item}</li>)}
</ul>
</div>Slots
---
// Layout.astro
---
<div class="layout">
<header>
<slot name="header" />
</header>
<main>
<slot /> <!-- default slot -->
</main>
<footer>
<slot name="footer">
<p>Default footer content</p>
</slot>
</footer>
</div>Routing & Pages
File-based routing and dynamic routes
File-Based Routing
src/pages/
├── index.astro → /
├── about.astro → /about
├── blog/
│ ├── index.astro → /blog
│ ├── post-1.astro → /blog/post-1
│ └── [slug].astro → /blog/:slug
├── api/
│ └── posts.json.ts → /api/posts.json
└── _hidden.astro → Not routed (underscore)Dynamic Routes
---
// pages/posts/[slug].astro
export async function getStaticPaths() {
const posts = await fetch('https://api.example.com/posts')
.then(res => res.json());
return posts.map(post => ({
params: { slug: post.slug },
props: { post }
}));
}
const { post } = Astro.props;
const { slug } = Astro.params;
---
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>Content Collections
Type-safe content management with Markdown/MDX
Define Collection
// src/content.config.ts
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/data/blog" }),
schema: z.object({
title: z.string(),
pubDate: z.date(),
author: z.string(),
tags: z.array(z.string()),
draft: z.boolean().optional(),
}),
});
export const collections = { blog };Query Collections
---
import { getCollection, getEntry } from 'astro:content';
// Get all entries
const allPosts = await getCollection('blog');
// Filter entries
const publishedPosts = await getCollection('blog', ({ data }) => {
return data.draft !== true;
});
// Get single entry
const post = await getEntry('blog', 'my-post-slug');
---Render Content
---
// pages/blog/[id].astro
import { getCollection, render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { id: post.id },
props: { post },
}));
}
const { post } = Astro.props;
const { Content, headings } = await render(post);
---
<article>
<h1>{post.data.title}</h1>
<time>{post.data.pubDate.toLocaleDateString()}</time>
<!-- Table of contents -->
<nav>
<ul>
{headings.map(h => (
<li style={\`margin-left: \${h.depth * 10}px\`}>
<a href={\`#\${h.slug}\`}>{h.text}</a>
</li>
))}
</ul>
</nav>
<Content />
</article>Framework Components
Using React, Vue, Svelte with client directives
Client Directives
---
import Counter from './Counter.jsx';
---
<!-- Default: static HTML only, no JS -->
<Counter />
<!-- Load immediately on page load -->
<Counter client:load />
<!-- Load when browser is idle -->
<Counter client:idle />
<!-- Load when scrolled into view -->
<Counter client:visible />
<!-- Load on media query match -->
<Counter client:media="(max-width: 768px)" />
<!-- Only render on client, skip SSR -->
<Counter client:only="react" />Mix Frameworks
---
import ReactCounter from './ReactCounter.jsx';
import VueButton from './VueButton.vue';
import SvelteCard from './SvelteCard.svelte';
---
<div>
<ReactCounter client:load />
<VueButton client:idle />
<SvelteCard client:visible />
</div>Images & Assets
Optimized image handling with Image component
Image Component
---
import { Image } from 'astro:assets';
import myImage from '../assets/my-image.png';
---
<!-- Local image (src/) -->
<Image src={myImage} alt="Description" />
<!-- Public folder image -->
<Image
src="/images/photo.jpg"
alt="Description"
width={600}
height={400}
/>
<!-- Remote image (requires config) -->
<Image
src="https://example.com/image.jpg"
alt="Description"
width={600}
height={400}
/>View Transitions
Animated page transitions and SPA routing
Enable Transitions
---
// Layout.astro
import { ClientRouter } from 'astro:transitions';
---
<html>
<head>
<ClientRouter />
</head>
<body>
<slot />
</body>
</html>Transition Control
---
import { navigate } from 'astro:transitions/client';
---
<!-- Disable transitions for specific link -->
<a href="/about" data-astro-reload>Full page reload</a>
<!-- Control history behavior -->
<a href="/page" data-astro-history="replace">
Replace history
</a>
<script>
// Programmatic navigation
navigate('/dashboard', {
history: 'push' // or 'replace', 'auto'
});
// Listen to lifecycle events
document.addEventListener('astro:page-load', () => {
console.log('New page loaded');
// Re-initialize scripts
});
</script>API Routes & Endpoints
Server endpoints and API route handling
Create Endpoints
// pages/api/posts.json.ts
import type { APIRoute } from 'astro';
export const GET: APIRoute = async ({ params, request }) => {
const posts = [
{ id: 1, title: 'Post 1' },
{ id: 2, title: 'Post 2' },
];
return new Response(JSON.stringify(posts), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
};
export const POST: APIRoute = async ({ request }) => {
const data = await request.json();
return new Response(JSON.stringify({ success: true, data }), {
status: 201,
headers: { 'Content-Type': 'application/json' }
});
};Modern Astro (Server Islands, Actions, Sessions)
Server Islands, Actions, and session middleware (Astro 4.15+ / 5+)
Server Islands
Static page + dynamic server-rendered components streamed in
---
// src/pages/product/[id].astro
import StaticInfo from '../../components/StaticInfo.astro'
import Cart from '../../components/Cart.astro'
---
<html>
<body>
<StaticInfo id={Astro.params.id} />
{/* server:defer renders this on the server AFTER the static HTML ships */}
<Cart server:defer>
<CartFallback slot="fallback" />
</Cart>
</body>
</html>Astro Actions
Type-safe server functions callable from the client (Astro 4.15+)
// src/actions/index.ts
import { defineAction } from 'astro:actions'
import { z } from 'astro:schema'
export const server = {
createComment: defineAction({
accept: 'form',
input: z.object({
postId: z.string(),
body: z.string().min(1).max(2000),
}),
handler: async ({ postId, body }, { cookies }) => {
const user = cookies.get('userId')?.value
if (!user) throw new Error('Not signed in')
return db.comment.create({ data: { postId, body, userId: user } })
},
}),
}
// Use from a client component
<form method="POST" action={actions.createComment}>
<input name="postId" value={post.id} hidden />
<textarea name="body"></textarea>
<button>Comment</button>
</form>Sessions & Middleware
Built-in sessions (5.1+) and the middleware request/response chain
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware'
export const onRequest = defineMiddleware(async (context, next) => {
// Read or write per-request session data
const cart = await context.session?.get('cart') ?? []
context.locals.cart = cart
const res = await next()
return res
})
// astro.config.mjs — enable experimental sessions
import { defineConfig } from 'astro/config'
export default defineConfig({
experimental: { session: true },
session: { driver: 'fs' }, // or 'redis', 'cloudflare-kv', etc.
})