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.

bash
npm create astro@latest

Output: interactive scaffolder; clones a starter, installs deps, optionally runs astro add

bash
npm install astro

Output: added astro to dependencies

bash
npx astro --help

Output: prints CLI usage

bash
pnpm create astro@latest

Output: runs the scaffolder via pnpm

bash
pnpm add astro

Output: added 1 package, linked from store

bash
yarn create astro

Output: scaffolds a new project

bash
yarn add astro

Output: added astro

bash
bun create astro

Output: scaffolds a new project via Bun

bash
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.x line. 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 check before they bite.
  • TypeScript types ship in-tree; astro:content generates additional types into .astro/types.d.ts at 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

FrameworkTrade-off
next (Next.js)App-first React framework with full SSR/ISR/edge. Heavier client bundle.
nuxtVue equivalent of Next; full-fat SSR, file-based routing.
sveltekitSvelte's SSR framework. Slim runtime, signals-style reactivity.
remixReact-Router-based SSR framework focused on web fundamentals. Now part of React Router itself.
qwik / qwik-cityResumability instead of hydration — even less JS than islands. Smaller ecosystem.
eleventyPure static-site generator. Zero JS framework runtime. Best for sites with no interactivity.
hugo / jekyll / zolaNon-JS static generators. Faster builds, narrower template languages.

Common gotchas

  1. Integration order matters in astro.config.mjs. Some integrations expect to run before/after others (e.g. tailwind before any integration that produces CSS). Follow the order in the docs; reordering can break style application without an explicit error.
  2. astro:content types regenerate on astro sync / build. Editing schemas in src/content.config.ts without running astro sync (or astro check) leaves your IDE pointing at stale .astro/types.d.ts — TypeScript errors surface only at build time.
  3. 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 adjusting prerender = true|false on individual routes.
  4. Server endpoints (.ts files under pages/) only run if output"static". Adding an API route to a static project silently does nothing at build time — they only ship when an adapter is configured.
  5. Hydration directives are island-scoped. client:load, client:idle, client:visible, client:media, client:only each have different cost/timing trade-offs. Defaulting to client:load everywhere defeats the islands model.
  6. MDX inside content collections must be configured. Adding .mdx files without @astrojs/mdx silently skips them at build. Run npx astro add mdx.
  7. Pagefind / search must run AFTER astro build. Astro's build produces dist/; only then can a post-build indexer like Pagefind walk the HTML.
  8. Astro.cookies / Astro.request requires SSR. Reading cookies in a static-output page returns undefined at build time. Mark the route with export const prerender = false to opt into server rendering.
  9. 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 preview before 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.

typescript
// 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 };
astro
---
// 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).

typescript
// 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" }),
});
typescript
// 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.

bash
npx astro add react

Output: installs @astrojs/react and patches astro.config.mjs.

astro
---
// 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>
tsx
// 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.

typescript
// 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.

typescript
// 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}!`,
  }),
};
tsx
// 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

bash
npx astro add cloudflare

Output: installs @astrojs/cloudflare, patches config, sets output: "server" (or hybrid via prerender).

bash
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

bash
npx astro add vercel

Output: installs @astrojs/vercel and configures the build for Vercel's serverless / edge functions.

bash
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

bash
npx astro add netlify

Output: installs @astrojs/netlify.

bash
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:

bash
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:

bash
npx astro add node

Output: (none — exits 0 on success)

bash
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:load ships JS in the critical path. client:idle and client:visible defer until after the page is interactive. Prefer visible for 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 (from astro: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 in astro.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 --verbose and inspect dist/_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.
  • Image component on Cloudflare. Default image processor (sharp) won't run on Workers. Use the Cloudflare-specific compileImage or 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.

FromToNotable changes (verify against the upgrade guide)
astro@3astro@4Image API renamed (@astrojs/image removed, replaced by astro:assets). New view-transitions API. Node 18.14+ required.
astro@4astro@5Content Layer API replaces the old content-collection loader. New astro:content types. Astro DB stable. New prerender defaults. Several legacy APIs removed.
AnyLatestNew rendering hints (server: { responsive: true }, i18n: { ... }), Actions stabilisation, Cache Components-style features depending on minor.

Recommended upgrade flow:

  1. Read the official migration guide for every major between current and target.
  2. npx @astrojs/upgrade runs the official codemod / dep upgrader. Inspect the diff.
  3. Run astro check — most breaking changes surface as TypeScript errors.
  4. Run astro build locally. Many runtime regressions show up only at build.
  5. 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.

ConcernPattern
Astro project module typeESM only. Top-level config in astro.config.mjs (note the .mjs extension).
TypeScripttsconfig.json extends astro/tsconfigs/strict (or strictest). "moduleResolution": "bundler" is recommended.
.astro componentsCompiled by Astro's own compiler (Rust-based). Server-side script runs in Node ESM; client-side scripts in the browser.
Markdown / MDXParsed at build time by remark (markdown) / MDX. The result is JSX/HTML; no runtime cost.
Front-end framework integrationsEach integration wires its own bundler glue. @astrojs/react, @astrojs/vue, etc., all expect ESM source.
Vite under the hoodAstro's bundler is Vite. Vite plugins work mostly transparently; some need an astro:integration wrapper.
CJS depsVite converts CJS to ESM at bundle time. Mostly transparent. Some libraries break — audit and prefer ESM packages where possible.
Adapter outputThe 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.

IntegrationRole
@astrojs/react, @astrojs/preact, @astrojs/vue, @astrojs/svelte, @astrojs/solid-js, @astrojs/lit, @astrojs/alpinejsUI framework adapters. Each enables <Component client:* /> islands.
@astrojs/tailwind (or Tailwind v4's Vite plugin)Tailwind CSS integration.
@astrojs/mdx, @astrojs/markdocRich markdown formats with components inside content.
@astrojs/sitemap, @astrojs/rssGenerated sitemap and RSS feed.
@astrojs/partytownMove third-party scripts off the main thread.
@astrojs/dbBuilt-in DB integration with libSQL / Turso.
@astrojs/cloudflare, @astrojs/vercel, @astrojs/netlify, @astrojs/node, @astrojs/denoDeploy adapters.
astro-iconIcon system using iconify.
astro-seoSEO meta-tag component.
astro-compressBuild-time HTML / CSS / JS / image compression.
astro-pagefind, pagefindStatic-site search; index after build.
@expressive-code/astro (now Starlight built-in)Code-block syntax highlighting + diff / line-highlight markers.
@astrojs/starlightDocumentation-site framework on top of Astro.
astro-i18next, astro-i18ni18n. 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.

bash
npm install -D vitest

Output: (none — exits 0 on success)

typescript
// 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:

typescript
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:

bash
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

yaml
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. The astro:security policy 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 input schema to defineAction. Without it, the action accepts any payload.
  • Astro.cookies — set with httpOnly: true, secure: true, sameSite: "lax" (or "strict") for any auth cookie. Never use path: "/" 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 in image.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 .astro syntax 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