Build Astro websites using components, pages, layouts, content collections, routing, SSR, View Transitions, and integrations. Use when creating Astro projects, .astro files, content collections, server islands, actions, or when asking about Astro patterns, Islands Architecture, or static site generation.
Astro is a web framework for building content-driven websites. Ships zero JavaScript by default using Islands Architecture.
# Create new project (choose your package manager)
npm create astro@latest
pnpm create astro@latest
bun create astro@latest
# Add integrations
npx astro add react tailwind
bunx astro add react tailwind
| Task | npm | bun |
|---|---|---|
| Create project | npm create astro@latest | bun create astro@latest |
| Dev server | npm run dev | bun dev |
| Build | npm run build | bun run build |
| Preview | npm run preview |
bun preview |
| Add integration | npx astro add <name> | bunx astro add <name> |
| Check types | npx astro check | bunx astro check |
src/
├── pages/ # File-based routing (REQUIRED)
├── components/ # Reusable .astro components
├── layouts/ # Page templates with <slot/>
├── content/ # Content collections
├── styles/ # CSS/SCSS files
├── assets/ # Optimized images
public/ # Static assets (unprocessed)
astro.config.mjs # Configuration
content.config.ts # Collection schemas
---
// Component Script (runs on server, never sent to client)
import Header from './Header.astro';
import { getCollection } from 'astro:content';
interface Props {
title: string;
count?: number;
}
const { title, count = 0 } = Astro.props;
const posts = await getCollection('blog');
---
<!-- Component Template -->
<Header />
<h1>{title}</h1>
<ul>
{posts.map(post => <li>{post.data.title}</li>)}
</ul>
<style>
/* Scoped by default */
h1 { color: blue; }
</style>
<style is:global>
/* Global styles */
</style>
File-based routing from src/pages/:
| File | Route |
|---|---|
index.astro | / |
about.astro | /about |
blog/[slug].astro | /blog/:slug |
[...path].astro | catch-all |
---
// src/pages/posts/[slug].astro
export function getStaticPaths() {
return [
{ params: { slug: 'post-1' }, props: { title: 'First' } },
{ params: { slug: 'post-2' }, props: { title: 'Second' } },
];
}
const { slug } = Astro.params;
const { title } = Astro.props;
---
<h1>{title}</h1>
---
// src/layouts/BaseLayout.astro
interface Props {
title: string;
}
const { title } = Astro.props;
---
<html lang="en">
<head>
<title>{title}</title>
</head>
<body>
<slot /> <!-- Child content injected here -->
</body>
</html>
Usage:
---
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout title="Home">
<h1>Welcome</h1>
</BaseLayout>
Define in src/content.config.ts:
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';
const blog = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
schema: z.object({
title: z.string(),
pubDate: z.coerce.date(),
draft: z.boolean().optional(),
}),
});
export const collections = { blog };
Query collections:
---
import { getCollection, getEntry, render } from 'astro:content';
const posts = await getCollection('blog', ({ data }) => !data.draft);
const post = await getEntry('blog', 'my-post');
const { Content } = await render(post);
---
<Content />
Add framework support:
# npm
npx astro add react # React 19 support
npx astro add vue # Vue 3
npx astro add svelte # Svelte 5
npx astro add solid # SolidJS
npx astro add preact # Preact
# bun
bunx astro add react vue svelte # Can add multiple at once
Use client:* directives for hydration:
---
import Counter from './Counter.jsx';
---
<!-- Static by default (no JS) -->
<Counter />
<!-- Hydrated on page load -->
<Counter client:load />
<!-- Hydrated when visible -->
<Counter client:visible />
<!-- Hydrated on idle -->
<Counter client:idle />
<!-- Hydrated on media query -->
<Counter client:media="(max-width: 768px)" />
Enable on-demand rendering:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
output: 'server', // or 'static' (default)
adapter: node({ mode: 'standalone' }),
});
Per-page opt-in/out:
---
export const prerender = false; // Render on-demand
// export const prerender = true; // Pre-render at build
---
Access request data:
---
const cookie = Astro.cookies.get('session');
const url = Astro.url;
const headers = Astro.request.headers;
---
Defer component rendering until content is ready:
---
import Avatar from './Avatar.astro';
---
<Avatar server:defer>
<div slot="fallback">Loading...</div>
</Avatar>
Type-safe backend functions in src/actions/index.ts:
import { defineAction, ActionError } from 'astro:actions';
import { z } from 'astro/zod';
export const server = {
subscribe: defineAction({
accept: 'form',
input: z.object({
email: z.string().email(),
}),
handler: async ({ email }, ctx) => {
if (!ctx.cookies.has('session')) {
throw new ActionError({ code: 'UNAUTHORIZED' });
}
// Process subscription
return { success: true };
},
}),
};
Call from client:
<script>
import { actions } from 'astro:actions';
const { data, error } = await actions.subscribe({ email: '[email protected]' });
</script>
Enable SPA-like navigation:
---
import { ClientRouter } from 'astro:transitions';
---
<head>
<ClientRouter />
</head>
<!-- Animate elements -->
<div transition:animate="slide">
<img transition:name="hero" transition:persist />
---
import { Image, Picture } from 'astro:assets';
import myImage from '../assets/hero.png';
---
<Image src={myImage} alt="Hero" />
<Image src="/public-image.jpg" alt="Public" width={800} height={600} />
<Picture src={myImage} formats={['avif', 'webp']} alt="Hero" />
<style>
/* Scoped to component */
h1 { color: red; }
</style>
<style is:global>
/* Global styles */
</style>
<style define:vars={{ color: 'blue' }}>
h1 { color: var(--color); }
</style>
npx astro add tailwind
bunx astro add tailwind
npx astro add mdx
bunx astro add mdx
Then use .mdx files in content collections or pages:
---