cheat sheet
astro
Package-level reference for the Astro framework on npm — install, integration ecosystem, runtime support, and SSR/SSG adapter gotchas.
astro
What it is
astro is a content-first web framework with an islands-architecture rendering model — pages are server-rendered to static HTML by default, and individual interactive components ("islands") hydrate selectively in the browser. Astro components mix JSX-like template syntax with frontmatter-scoped JavaScript and can interoperate with React, Vue, Svelte, Solid, Preact, and Lit via official integrations.
Reach for astro when most of the site is content (docs, blogs, marketing, cheat sheets) with islands of interactivity. Reach for Next.js / Nuxt / SvelteKit if the whole app is interactive, or for static-only generators like Eleventy if you want zero runtime JavaScript framework code.
Install
Astro is both a framework and a CLI (astro dev, astro build). Use the official scaffolder for new projects.
npm create astro@latest
Output: interactive scaffolder; clones a starter, installs deps, optionally runs astro add
npm install astro
Output: added astro to dependencies
npx astro --help
Output: prints CLI usage
pnpm create astro@latest
Output: runs the scaffolder via pnpm
pnpm add astro
Output: added 1 package, linked from store
yarn create astro
Output: scaffolds a new project
yarn add astro
Output: added astro
bun create astro
Output: scaffolds a new project via Bun
bun add astro
Output: installed astro
Versioning & Node support
Current line is astro@5.x (released late 2024). Astro ships major versions roughly yearly with named codenames.
- Node 18.20.8+, 20.3.0+, 22+, or 24+ on the
5.xline. Older Node is rejected at install time. - ESM-only — Astro projects must use
"type": "module". - Strict semver across majors. Most breaking changes are flagged via
astro checkbefore they bite. - TypeScript types ship in-tree;
astro:contentgenerates additional types into.astro/types.d.tsat build time.
Package metadata
- Maintainer: The Astro Technology Company (the for-profit org behind Astro) + community
- Project home: github.com/withastro/astro
- Docs: docs.astro.build
- npm: npmjs.com/package/astro
- License: MIT
- First released: 2021
- Downloads: several million weekly downloads — fast-growing within the static-site / content space.
Peer dependencies & extras
Astro has a huge integration ecosystem published under @astrojs/*. The core astro package is the renderer + CLI; integrations are added separately via npx astro add <name> which patches astro.config.mjs and package.json.
UI framework integrations:
@astrojs/react— render React islands@astrojs/preact,@astrojs/vue,@astrojs/svelte,@astrojs/solid-js,@astrojs/lit@astrojs/alpinejs— Alpine.js sprinkles
Styling / content:
@astrojs/tailwind— Tailwind CSS integration@astrojs/mdx— MDX support inside content collections@astrojs/markdoc— Markdoc support@astrojs/sitemap,@astrojs/rss— generated sitemap and RSS feeds@astrojs/partytown— third-party script ofloading
Deploy adapters (output: "server" or "hybrid"):
@astrojs/cloudflare— Cloudflare Pages / Workers@astrojs/vercel— Vercel@astrojs/netlify— Netlify@astrojs/node— self-hosted Node server@astrojs/deno— Deno Deploy
Companion library:
astro-icon,astro-seo,astro-compress— popular third-party plugins
Alternatives
| Framework | Trade-off |
|---|---|
next (Next.js) | App-first React framework with full SSR/ISR/edge. Heavier client bundle. |
nuxt | Vue equivalent of Next; full-fat SSR, file-based routing. |
sveltekit | Svelte's SSR framework. Slim runtime, signals-style reactivity. |
remix | React-Router-based SSR framework focused on web fundamentals. Now part of React Router itself. |
qwik / qwik-city | Resumability instead of hydration — even less JS than islands. Smaller ecosystem. |
eleventy | Pure static-site generator. Zero JS framework runtime. Best for sites with no interactivity. |
hugo / jekyll / zola | Non-JS static generators. Faster builds, narrower template languages. |
Common gotchas
- Integration order matters in
astro.config.mjs. Some integrations expect to run before/after others (e.g.tailwindbefore any integration that produces CSS). Follow the order in the docs; reordering can break style application without an explicit error. astro:contenttypes regenerate onastro sync/ build. Editing schemas insrc/content.config.tswithout runningastro sync(orastro check) leaves your IDE pointing at stale.astro/types.d.ts— TypeScript errors surface only at build time.- Rendering strategy is a project-wide decision.
output: "static","server", or"hybrid"changes the entire build pipeline. Switching mid-project often requires re-adding the deploy adapter and adjustingprerender = true|falseon individual routes. - Server endpoints (
.tsfiles underpages/) only run ifoutput≠"static". Adding an API route to a static project silently does nothing at build time — they only ship when an adapter is configured. - Hydration directives are island-scoped.
client:load,client:idle,client:visible,client:media,client:onlyeach have different cost/timing trade-offs. Defaulting toclient:loadeverywhere defeats the islands model. - MDX inside content collections must be configured. Adding
.mdxfiles without@astrojs/mdxsilently skips them at build. Runnpx astro add mdx. - Pagefind / search must run AFTER
astro build. Astro's build producesdist/; only then can a post-build indexer like Pagefind walk the HTML. Astro.cookies/Astro.requestrequires SSR. Reading cookies in a static-output page returnsundefinedat build time. Mark the route withexport const prerender = falseto opt into server rendering.- The dev server and the build output can drift. Some integrations (image processing, fonts) behave differently in dev vs build. Always run
astro build && astro previewbefore claiming "it works".
Real-world recipes
These recipes target architectural decisions — output mode, integration setup, middleware — that the companion's API-focused article doesn't cover.
SSG site with content collections
The bread-and-butter Astro use case: a docs / blog / cheat-sheet site with type-checked content.
// src/content.config.ts
import { defineCollection, z } from "astro:content";
import { glob } from "astro/loaders";
const articles = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/articles" }),
schema: z.object({
title: z.string(),
description: z.string(),
updated: z.coerce.date(),
tags: z.array(z.string()).default([]),
}),
});
export const collections = { articles };
---
// src/pages/articles/[...slug].astro
import { getCollection, render } from "astro:content";
export async function getStaticPaths() {
const entries = await getCollection("articles");
return entries.map((entry) => ({ params: { slug: entry.id }, props: { entry } }));
}
const { entry } = Astro.props;
const { Content } = await render(entry);
---
<article>
<h1>{entry.data.title}</h1>
<p>{entry.data.description}</p>
<Content />
</article>
Output: every markdown file in src/content/articles/ becomes a static page at /articles/<slug>.
Hybrid: static site with one SSR route
Useful when 99% of routes are content but a single endpoint needs request-time logic (search, form handler, auth callback).
// astro.config.mjs
import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";
export default defineConfig({
output: "static", // SSG by default
adapter: cloudflare({ mode: "directory" }),
});
// src/pages/api/search.ts
import type { APIRoute } from "astro";
export const prerender = false; // opt this route into SSR
export const GET: APIRoute = async ({ url }) => {
const q = url.searchParams.get("q");
return new Response(JSON.stringify({ query: q, results: [] }), {
headers: { "Content-Type": "application/json" },
});
};
Output: astro build produces a static dist/ plus a Workers function for /api/search.
React island inside an Astro page
Astro renders the host page server-side; the React island hydrates lazily.
npx astro add react
Output: installs @astrojs/react and patches astro.config.mjs.
---
// src/pages/dashboard.astro
import Layout from "../layouts/Layout.astro";
import Counter from "../components/Counter.tsx";
---
<Layout title="Dashboard">
<h1>Hi from Astro</h1>
<Counter client:visible />
</Layout>
// src/components/Counter.tsx
import { useState } from "react";
export default function Counter() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>Clicks: {n}</button>;
}
Output: the page renders fully static; the Counter component's React runtime loads only when the button enters the viewport.
Middleware-based redirect
A common need: enforce https:// or redirect a legacy path. Middleware is a single file at src/middleware.ts.
// src/middleware.ts
import { defineMiddleware } from "astro:middleware";
export const onRequest = defineMiddleware(async (ctx, next) => {
const url = new URL(ctx.request.url);
if (url.pathname.startsWith("/old/")) {
const next = url.pathname.replace("/old/", "/new/");
return ctx.redirect(next, 301);
}
return next();
});
Output: every request to /old/foo issues a 301 to /new/foo before Astro renders any page.
Astro DB / Actions for typed RPC
Astro 5 introduced "Actions" — typed server-side functions you can call from a client island as if they were local. They eliminate the boilerplate of a separate API route plus fetch wrapper.
// src/actions/index.ts
import { defineAction } from "astro:actions";
import { z } from "astro:schema";
export const server = {
greet: defineAction({
input: z.object({ name: z.string().min(1) }),
handler: async ({ name }) => `Hello, ${name}!`,
}),
};
// src/components/Greeting.tsx
import { actions } from "astro:actions";
import { useState } from "react";
export default function Greeting() {
const [msg, setMsg] = useState("");
return (
<form onSubmit={async (e) => {
e.preventDefault();
const { data, error } = await actions.greet({ name: "Alice" });
if (!error) setMsg(data);
}}>
<button type="submit">Say hi</button>
<p>{msg}</p>
</form>
);
}
Output: client island calls the typed greet action; Astro generates the HTTP route automatically. Actions require an adapter (i.e. output != "static") for the route they back.
Production deployment
Astro deploys to nearly every host — the deploy adapter abstracts the platform-specific glue. Pick one per project.
Cloudflare Pages
npx astro add cloudflare
Output: installs @astrojs/cloudflare, patches config, sets output: "server" (or hybrid via prerender).
npm run build
npx wrangler pages deploy ./dist --project-name=my-site
Output: the site is live at my-site.pages.dev and any custom domain configured in the dashboard.
For SSG-only projects, no adapter is needed — use wrangler pages deploy ./dist directly. See the npm-wrangler article for multi-env deploys.
Vercel
npx astro add vercel
Output: installs @astrojs/vercel and configures the build for Vercel's serverless / edge functions.
vercel
Output: Vercel detects Astro, runs astro build, and deploys.
The Vercel adapter has three modes: vercel/static (pure SSG, no functions), vercel/serverless (Node functions), and vercel/edge (Edge runtime). Pick edge for global low-latency, serverless for unrestricted Node APIs.
Netlify
npx astro add netlify
Output: installs @astrojs/netlify.
netlify deploy --build --prod
Output: deploys via Netlify CLI.
Netlify auto-detects Astro from astro.config.mjs and runs astro build in their build environment without a netlify.toml for many projects.
Static export to any host
For pure SSG (output: "static"), the build is just files under dist/. Deploy via:
npm run build
rsync -av --delete dist/ user@server:/var/www/site/
# or aws s3 sync dist/ s3://bucket --delete
# or gh-pages -d dist
Output: files served by any static host (S3 + CloudFront, GitHub Pages, Nginx, Apache, etc.).
Hybrid SSR
output: "static" + per-route prerender = false is the modern Astro idiom — most pages prerender, a few opt into SSR. The adapter handles routing automatically. For Node self-hosting:
npx astro add node
Output: (none — exits 0 on success)
node ./dist/server/entry.mjs
Output: Node server on HOST:PORT env vars (default localhost:4321). Run behind a reverse proxy (Nginx / Caddy) with HTTPS termination.
Performance tuning
Astro's default output is already optimised for ship-as-little-JS-as-possible. The tuning levers are about preventing regressions, not adding optimisations.
- Default hydration directive matters.
client:loadships JS in the critical path.client:idleandclient:visibledefer until after the page is interactive. Prefervisiblefor below-the-fold widgets. client:only="<framework>"skips server rendering entirely — useful for components that can't SSR (window-dependent libraries) but ships more JS. Avoid unless necessary.- Image optimisation. Astro's
<Image />and<Picture />components (fromastro:assets) handle resizing, format negotiation (AVIF / WebP), and lazy loading. Always use them for content images. - View transitions. Adding
<ViewTransitions />(now built into Astro 5) enables SPA-feel navigation with no JS framework. Modest cost, big perceived-perf win. prefetch. Enable inastro.config.mjs(prefetch: true). Astro prefetches links in the viewport on hover or intent. Combine with view transitions for instant nav.- Bundle splitting. Vite handles this automatically — each island ships in its own chunk. Audit with
astro build --verboseand inspectdist/_astro/. - Astro DB / Actions cold start. Adapter functions have cold-start cost. On Cloudflare Workers this is negligible; on Vercel Lambda it's measurable. Choose edge runtime when possible.
Imagecomponent on Cloudflare. Default image processor (sharp) won't run on Workers. Use the Cloudflare-specificcompileImageor proxy through Cloudflare Images.
Version migration guide
Astro ships a major roughly yearly with named codenames. Each major has a published migration guide and @astrojs/upgrade runs codemods. Check the official migration docs for your specific version range, since the platform evolves quickly.
| From | To | Notable changes (verify against the upgrade guide) |
|---|---|---|
astro@3 | astro@4 | Image API renamed (@astrojs/image removed, replaced by astro:assets). New view-transitions API. Node 18.14+ required. |
astro@4 | astro@5 | Content Layer API replaces the old content-collection loader. New astro:content types. Astro DB stable. New prerender defaults. Several legacy APIs removed. |
| Any | Latest | New rendering hints (server: { responsive: true }, i18n: { ... }), Actions stabilisation, Cache Components-style features depending on minor. |
Recommended upgrade flow:
- Read the official migration guide for every major between current and target.
npx @astrojs/upgraderuns the official codemod / dep upgrader. Inspect the diff.- Run
astro check— most breaking changes surface as TypeScript errors. - Run
astro buildlocally. Many runtime regressions show up only at build. - Bump every
@astrojs/*integration in lockstep — they're versioned independently but tightly coupled.
ESM/CJS interop & bundling
Astro is ESM-only. Projects MUST have "type": "module" in package.json and use import syntax everywhere.
| Concern | Pattern |
|---|---|
| Astro project module type | ESM only. Top-level config in astro.config.mjs (note the .mjs extension). |
| TypeScript | tsconfig.json extends astro/tsconfigs/strict (or strictest). "moduleResolution": "bundler" is recommended. |
.astro components | Compiled by Astro's own compiler (Rust-based). Server-side script runs in Node ESM; client-side scripts in the browser. |
| Markdown / MDX | Parsed at build time by remark (markdown) / MDX. The result is JSX/HTML; no runtime cost. |
| Front-end framework integrations | Each integration wires its own bundler glue. @astrojs/react, @astrojs/vue, etc., all expect ESM source. |
| Vite under the hood | Astro's bundler is Vite. Vite plugins work mostly transparently; some need an astro:integration wrapper. |
| CJS deps | Vite converts CJS to ESM at bundle time. Mostly transparent. Some libraries break — audit and prefer ESM packages where possible. |
| Adapter output | The adapter dictates output shape: Cloudflare → Workers modules (ESM), Node → ESM bundles, Vercel → mixed depending on mode. |
Plugin & ecosystem coverage
Astro's integration ecosystem is large. The full list is at astro.build/integrations.
| Integration | Role |
|---|---|
@astrojs/react, @astrojs/preact, @astrojs/vue, @astrojs/svelte, @astrojs/solid-js, @astrojs/lit, @astrojs/alpinejs | UI framework adapters. Each enables <Component client:* /> islands. |
@astrojs/tailwind (or Tailwind v4's Vite plugin) | Tailwind CSS integration. |
@astrojs/mdx, @astrojs/markdoc | Rich markdown formats with components inside content. |
@astrojs/sitemap, @astrojs/rss | Generated sitemap and RSS feed. |
@astrojs/partytown | Move third-party scripts off the main thread. |
@astrojs/db | Built-in DB integration with libSQL / Turso. |
@astrojs/cloudflare, @astrojs/vercel, @astrojs/netlify, @astrojs/node, @astrojs/deno | Deploy adapters. |
astro-icon | Icon system using iconify. |
astro-seo | SEO meta-tag component. |
astro-compress | Build-time HTML / CSS / JS / image compression. |
astro-pagefind, pagefind | Static-site search; index after build. |
@expressive-code/astro (now Starlight built-in) | Code-block syntax highlighting + diff / line-highlight markers. |
@astrojs/starlight | Documentation-site framework on top of Astro. |
astro-i18next, astro-i18n | i18n. Astro 4+ has built-in i18n config too. |
Testing & CI integration
Vitest for utilities and unit logic
Plain Vitest works for any .ts file in the project — utilities, content-collection loaders, action handlers. There's no Astro-specific runner.
npm install -D vitest
Output: (none — exits 0 on success)
// src/lib/format.test.ts
import { describe, it, expect } from "vitest";
import { formatTitle } from "./format";
describe("formatTitle", () => {
it("title-cases the input", () => {
expect(formatTitle("hello world")).toBe("Hello World");
});
});
Component tests via @astrojs/test
Astro has experimental support for component testing via container API:
import { experimental_AstroContainer as AstroContainer } from "astro/container";
import Card from "../src/components/Card.astro";
const container = await AstroContainer.create();
const html = await container.renderToString(Card, { props: { title: "Hi" } });
expect(html).toContain("Hi");
End-to-end with Playwright
For real-browser tests:
npx astro preview & # serve the built site
npx playwright test
Output: (none — exits 0 on success)
The playwright.config.ts should set webServer to start Astro preview automatically.
CI pipeline
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npm ci
- run: npx astro check # typecheck + content schema validation
- run: npm run build
- run: npm test
Security considerations
- CSP and islands. Each island ships a small bootstrap script. Inline scripts are unavoidable for current Astro versions. Use a nonce-based CSP or
'self' 'sha256-...'allowlists. Theastro:securitypolicy is still evolving — check current Astro docs for the recommended config. - Untrusted markdown. If you render markdown from user input via the content collection loader, sanitise the HTML output.
remark-based parsers default to safe, but allowing raw HTML (rehype-raw) opens XSS. - Action input validation. Always provide a Zod
inputschema todefineAction. Without it, the action accepts any payload. Astro.cookies— set withhttpOnly: true,secure: true,sameSite: "lax"(or"strict") for any auth cookie. Never usepath: "/"for tokens with a narrower scope.- API route auth. SSR routes have full access to env / secrets — protect them with a per-route auth check. Don't rely on "obscure URL" patterns.
- Image source URLs.
<Image src={userUpload} />fetches and processes the URL at build / request time. SSRF risk if the URL is attacker-controlled. Allowlist domains inimage.domains/image.remotePatterns. - Adapter-specific risks. Each adapter inherits its platform's risks: Vercel functions need careful env-var scoping; Cloudflare adapters need wrangler secret hygiene (see
npm-wrangler).
Troubleshooting common errors
Cannot find module 'astro:content' — types not generated. Run astro sync or restart the dev server.
astro:content types out of date in VS Code — VS Code's TS server cached stale types. Reload window or run astro sync.
output: "static" page using Astro.cookies returns undefined — that route prerenders at build time, so no request exists. Set export const prerender = false on the route and add an adapter.
MDX file silently skipped — @astrojs/mdx missing. npx astro add mdx.
Image() from astro:assets fails at build — image source not under src/ or public/, or remote URL not allowlisted. Add to image.remotePatterns in astro.config.mjs.
view transitions flash white between pages — <ViewTransitions /> not included in the layout, or per-page styles missing transition:persist. See current Astro docs for the view-transitions API.
Adapter deploy fails with "Cannot find package" — adapter not installed or out of sync with Astro major. Run npx astro add <adapter> to fix.
Hydration mismatch warning in console — server and client render diverged. Common causes: Date.now() / Math.random() in island components, locale-dependent formatting (use Intl with explicit locale).
Cannot serialize prop — passing a non-serialisable value (function, class instance) to a client island. Islands receive JSON-serialisable props only.
Dev server slow on Windows — Vite's file watcher hitting NTFS. Move project off OneDrive-synced paths; consider WSL.
When NOT to use this
Skip Astro when:
- The app is mostly interactive. A chat app, dashboard, design tool, or game spends most of its life on the client. Next.js / Nuxt / SvelteKit / Remix are designed for that workload; Astro's islands model becomes friction.
- You need true streaming SSR with partial pre-rendering. Next.js's App Router and React Server Components handle this natively. Astro 5 ships with streaming for SSR routes, but the React ecosystem is more mature here.
- The team is React-only and embedded. Astro's
.astrosyntax is a small learning hill but real. If the team will never write.astro, the islands model adds little value over Next.js. - You're targeting a non-web platform. Astro outputs HTML. For React Native / mobile / Electron / desktop, this isn't the framework.
- You want a zero-build site. Eleventy and Hugo are simpler if you don't need TypeScript, components, or client islands.
- The site has no content. A pure dashboard with no SEO-relevant pages doesn't benefit from Astro's SSG strengths.
See also
- JavaScript: astro — components, content collections, routing, layouts
- JavaScript: wrangler — deploying an Astro site to Cloudflare Pages
- JavaScript: react-basics — pairing React islands with Astro pages
- Concept: http — SSR vs SSG request/response model