Astro logoAstroINTERMEDIATE

Astro

Astro cheat sheet with islands architecture, component syntax, content collections, routing, and SSG/SSR patterns with code examples.

10 min read
astrostatic-siteislandsjavascripttypescriptssrview-transitions

Sign in to mark items as known and track your progress.

Sign in

Getting Started

Project setup and basic configuration

Create New Project

📄 Codebash
# 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, docs
✅ CLI guides you through setup with TypeScript, dependencies, and git
💡 Dev server runs on http://localhost:4321 by default
🔍 Official templates: minimal, blog, portfolio, docs
⚡ Supports React, Vue, Svelte, Solid, Preact, Alpine.js
setupcliinstall

Project Structure

text
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 configuration
✅ src/pages/ creates routes automatically via file name
💡 public/ files served at root without processing
🔍 src/content/ requires content.config.ts for collections
⚡ All file types supported: .astro, .js, .ts, .jsx, .tsx, .vue, .svelte
structureorganizationfiles

Components & Syntax

Astro component structure, props, and slots

Component Structure

astro
---
// 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>
✅ Components have two sections: script (---) and template (HTML)
💡 Script runs only on server at build time or on-demand
🔍 Use {expression} for JavaScript in template
⚡ Styles are scoped to component automatically
componentsyntaxstructure

Props & TypeScript

astro
---
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>
✅ Access props via Astro.props object
💡 Use interface Props for TypeScript type checking
🔍 Destructure with default values for optional props
⚡ Functions cannot be passed to hydrated framework components
propstypescripttypes

Slots

astro
---
// 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>
✅ <slot /> renders child content passed to component
💡 Named slots allow multiple content areas
🔍 Fallback content appears if slot is empty
⚡ Use Astro.slots API to check if slot has content
slotscompositionlayouts

Routing & Pages

File-based routing and dynamic routes

File-Based Routing

text
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)
✅ Each file in src/pages/ becomes a route automatically
💡 index.astro maps to parent directory path
🔍 Prefix with _ to exclude from routing
⚡ .astro, .md, .mdx, .html, .js, .ts all supported
routingfile-basedpages

Dynamic Routes

astro
---
// 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>
✅ getStaticPaths() generates all routes at build time
💡 Return array of { params, props } objects
🔍 Access params via Astro.params, props via Astro.props
⚡ Use [...rest] for catch-all routes of any depth
dynamic-routesgetStaticPathsparams

Content Collections

Type-safe content management with Markdown/MDX

Define Collection

typescript
// 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 };
✅ defineCollection() configures collection with loader and schema
💡 Use glob() loader for file-based content
🔍 Zod schema validates frontmatter at build time
⚡ Store content in any directory, specify with base option
contentcollectionsschemadefineCollection

Query Collections

astro
---
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');
---
✅ getCollection() fetches entire collection as array
💡 Filter function receives {id, data} for each entry
🔍 getEntry() fetches single entry by collection and id
⚡ Access frontmatter via .data, entry id via .id
getCollectiongetEntryquery

Render Content

astro
---
// 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>
✅ render() processes Markdown/MDX into renderable Content component
💡 Returns headings array for table of contents generation
🔍 Content component renders the processed HTML
⚡ Works with both Markdown and MDX files
rendercontentmarkdown

Framework Components

Using React, Vue, Svelte with client directives

Client Directives

astro
---
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" />
✅ Framework components render as static HTML by default
💡 client:* directives add interactivity (ship JavaScript)
🔍 client:load for above-fold critical components
⚡ client:visible for lazy loading below-the-fold content
clienthydrationislandsdirectives

Mix Frameworks

astro
---
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>
✅ Mix React, Vue, Svelte, Solid in same .astro file
💡 Each framework only ships its own runtime when used
🔍 Cannot mix frameworks within same framework component file
⚡ Supported props: strings, numbers, objects, arrays, dates, maps, sets
frameworksreactvuesveltemulti-framework

Images & Assets

Optimized image handling with Image component

Image Component

astro
---
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}
/>
✅ Image component automatically optimizes at build time
💡 Local images (src/) auto-detect dimensions to prevent CLS
🔍 Remote images need authorization in astro.config.mjs
⚡ Picture component generates multiple formats and sizes
imagesImageassetsoptimization

View Transitions

Animated page transitions and SPA routing

Enable Transitions

astro
---
// Layout.astro
import { ClientRouter } from 'astro:transitions';
---
<html>
  <head>
    <ClientRouter />
  </head>
  <body>
    <slot />
  </body>
</html>
✅ ClientRouter enables SPA-like navigation with animations
💡 Built-in animations: fade (default), slide, none, initial
🔍 transition:name pairs elements between old/new pages
⚡ transition:persist keeps elements across navigation
transitionsClientRouteranimationsspa

Transition Control

astro
---
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>
✅ navigate() triggers transitions programmatically
💡 data-astro-reload forces full page navigation
🔍 astro:page-load ideal for re-initializing scripts
⚡ Respects prefers-reduced-motion automatically
navigatelifecycleevents

API Routes & Endpoints

Server endpoints and API route handling

Create Endpoints

typescript
// 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' }
  });
};
✅ Export GET, POST, PUT, DELETE functions from .ts/.js files
💡 File extension determines output: .json.ts → .json
🔍 Access params, request, redirect from context object
⚡ SSR mode: live routes. Static mode: pre-rendered files
apiendpointsroutesAPIRoute

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

astro
---
// 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>
💡 Static HTML + server-streamed chunks in one response — no client JS to hydrate
📌 Requires an adapter and a deployment target that supports streaming
⚡ Always provide a <slot name="fallback"> so the static shell renders cleanly
🎯 Use for cart, "recently viewed", per-user banners on cacheable marketing pages
server-islandsstreamingmodern

Astro Actions

Type-safe server functions callable from the client (Astro 4.15+)

typescript
// 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>
💡 Astro Actions = type-safe server RPC with zod validation built in
📌 accept: "form" makes the action work as a plain progressive HTML form
⚡ ActionError codes map cleanly to HTTP statuses — use them, don't hand-roll
🎯 Pair with islands for forms that work without JS but enhance with it
actionsrpcforms

Sessions & Middleware

Built-in sessions (5.1+) and the middleware request/response chain

typescript
// 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.
})
💡 Middleware runs on every request — set locals there, read in pages
⚡ sequence() composes multiple middlewares cleanly in render order
📌 Sessions are experimental in 5.1+; pick a driver that matches your host
🎯 Always declare App.Locals and App.SessionData in env.d.ts for full type safety
middlewaresessionsauth