Skip to content

Deployment & Ecosystem

astro build produces your deployable output in dist/. What ends up there depends on your rendering mode:

  • Static — dist/ is a folder of .html files, CSS, and island JavaScript. It’s just files.
  • On-demand / hybrid — dist/ also contains a server entry in the format your adapter targets (a Cloudflare Worker, a Node server, a serverless function), plus the static assets.

Wire type-checking into the build so a type error stops a bad deploy: astro check && astro build.

The single biggest deploy decision is static vs on-demand — it determines where you can host:

Static On-demand (SSR)
Output HTML files Server entry + assets
Needs Any static host / CDN A runtime (via an adapter)
Hosts Cloudflare Pages, Netlify, GitHub Pages, S3, any CDN Cloudflare Workers, Node server, Vercel, Netlify Functions
Best for Content that’s the same for everyone Per-request data: auth, forms, live data
Adapter Not required Required

A purely static site deploys anywhere that serves files. The moment any route is on-demand (or uses server islands or Actions), you need the matching adapter configured — deploying without it is the classic “an adapter is required” error.

flowchart TB
  build["astro build"] --> static["Static: HTML files"]
  build --> ssr["On-demand: server entry + assets"]
  static --> cdn["any CDN / static host"]
  ssr --> runtime["adapter runtime: Workers, Node, serverless"]
Where a build can go

The current adapters, and Cloudflare’s two modes

Section titled “The current adapters, and Cloudflare’s two modes”

Every official adapter’s latest version, verified with npm view <pkg> version against the real npm registry (not remembered from a changelog):

Adapter Package Latest version
Node @astrojs/node 11.1.6
Cloudflare @astrojs/cloudflare 14.3.3
Vercel @astrojs/vercel 11.0.11
Netlify @astrojs/netlify 8.2.6

Cloudflare is worth a second look because it covers both deploy targets this lesson describes, from one package: a purely static Astro site can deploy to Cloudflare Pages/Workers as static assets with no adapter at all (just files, served from Cloudflare’s CDN); the moment a route needs on-demand rendering, adapter: cloudflare() turns the same project into a Cloudflare Worker that runs your server code per request — the adapter is what adds the “assets” binding and worker entry (dist/_worker.js/index.js) to the Cloudflare deploy config, not something a static-only deploy needs.

astro preview starts a local server for your build output — but whether it can actually run on-demand routes depends entirely on whether the adapter you’re using ships its own preview server. Straight from Astro’s own reference: “If an adapter is set in the configuration, the preview server is provided by the adapter. Adapters are not required to provide a preview server, so this feature may not be available depending on your adapter of choice.” Verified directly against each adapter’s own published exports map:

Adapter Ships a preview entrypoint?
@astrojs/node Yes (./preview.js)
@astrojs/cloudflare Yes (./entrypoints/preview)
@astrojs/vercel No
@astrojs/netlify No

On Vercel or Netlify, astro preview still runs, but it can only serve the static parts of your build — there’s no local preview of the on-demand routes for those two, by design; you preview those with the platform’s own local dev tool (vercel dev, netlify dev) instead. astro preview also explicitly says it “is not designed to be run in production,” regardless of adapter.

Two config values bite people at deploy time:

  • site must be your real deployed URL (e.g. https://example.com). Sitemaps, canonical tags, and RSS all build absolute URLs from it — wrong site, wrong links.
  • base must match the subpath you’re served from. Deploy to example.com/docs/ and you set base: '/docs', then reference internal links with the base prefix. Serve at the domain root and you omit it. A mismatched base is the usual cause of “the deployed site’s CSS and links are all 404” — the assets are looked up at the wrong path.

Astro is content-first, and its ecosystem reflects that:

  • Starlight — an official documentation-site framework built on Astro (it’s what this course is built with): sidebars, search, i18n, and dark mode out of the box.
  • Integrations directory — a large catalog of official and community integrations: UI frameworks, CMS connectors, image services, analytics, and adapters for every major host.
  • Content-focused patterns — combined with content collections, Astro is a natural fit for blogs, docs, marketing sites, and headless-CMS front-ends, where you get a fast static (or on-demand) site with typed content.

Two packages worth a specific warning, both verified directly against their real npm metadata rather than a blog post’s memory of them:

  • @astrojs/db — npm view @astrojs/db deprecated returns: “This package is deprecated. Use a database client (Drizzle, Kysely, etc.) directly instead.” Reach for node:sqlite, Drizzle, or a hosted database (Turso, Neon, PlanetScale) instead.
  • astro-integration-kit — also npm-deprecated (npm view astro-integration-kit deprecated points at its own migration guide). If a generated snippet reaches for it to build a custom integration, check whether the helper it’s using has since landed in Astro core itself before adding the extra dependency.

Reach for a framework like Starlight when it matches your use case rather than rebuilding docs infrastructure by hand — the same “compose, don’t reinvent” instinct that runs through Astro.

Most projects upgrade in an afternoon. The checklist:

  1. Node 22.12+ is the floor (unchanged from Astro 6). Run npx @astrojs/upgrade to bump astro and every @astrojs/* integration together.
  2. Remove the stabilised experimental flags: experimental.cache, experimental.routeRules, experimental.logger move to top-level cache, routeRules, logger; experimental.rustCompiler, queuedRendering, advancedRouting are deleted (now default).
  3. Fix what the Rust compiler now rejects: unclosed tags and invalid nesting are build errors. The error messages point at the file and line.
  4. Check whitespace: compressHTML defaults to 'jsx'. Add {" "} where inline elements need a space, or set compressHTML: true temporarily.
  5. Markdown plugins: if you use remark/rehype plugins, install @astrojs/markdown-remark and set markdown: { processor: unified() }; otherwise enjoy Sätteri.
  6. Vite 8: review the Vite 8 migration notes for any Vite-specific plugin you configured under the vite key.
  7. Rename src/fetch.ts if you had an unrelated file by that name (or set fetchFile).
  8. Removed APIs: @astrojs/db (switch to node:sqlite, Drizzle, or a hosted DB), the astro:transitions event constants (use the string event names such as 'astro:before-preparation'), and getContainerRenderer now imported from a framework-specific subpath (@astrojs/preact/container-renderer if you use Preact — the exact subpath matches whichever @astrojs/<framework> package the project actually installs, verified in the testing lesson).

Then astro check && astro build — the build is noticeably faster, which is the reward.

A generated “here’s how to deploy Astro” answer is worth checking against what’s actually true on 7.3.5:

  • Does it say an adapter is optional for server islands or Actions on an otherwise-static site? Proven above (and in the rendering-modes lesson): those features need on-demand rendering to run at all, which needs an adapter, even if every page route is prerendered.
  • Does it recommend @astrojs/db or astro-integration-kit without a caveat? Both are npm-deprecated — proven above with their real deprecation notices, not a guess about their popularity.
  • Does it claim astro preview is a valid lightweight production server? Proven above with the CLI’s own words: “not designed to be run in production.”
  • Does it assume astro preview exercises on-demand routes the same way regardless of adapter? Proven above: only Node and Cloudflare ship a preview entrypoint; Vercel and Netlify don’t, so astro preview only serves static output on those two.
  • Does it suggest Cloudflare always needs adapter: cloudflare() to deploy? A purely static site deploys to Cloudflare Pages/Workers as static assets with zero adapter — the adapter is specifically for on-demand rendering, proven above.

🐛 Spot the bug

astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel';
export default defineConfig({
adapter: vercel(),
});
Show the answer

Nothing here is a config error — but a team relying on astro preview to manually verify an on-demand route before deploying to Vercel is trusting a step that, proven above, doesn’t actually run that route: @astrojs/vercel’s package exports has no preview entrypoint, so astro preview on this config only serves the static parts of the build. The fix isn’t a code change to this file — it’s a process one: use vercel dev (or deploy to a Vercel preview environment) to actually exercise on-demand routes before shipping, since astro preview silently can’t.

A deploy target is really two separate questions Astro answers independently: “where do the files go” (any static host, for the HTML/CSS/JS dist/client produces) and “who runs the server code” (the adapter, translating Astro’s on-demand rendering into that one platform’s own runtime shape). Cloudflare answering both — plain static assets or a Worker, from the same package — is the clearest illustration that these are genuinely separate axes, not one combined “hosting provider” decision.

Static deploy On-demand deploy
Adapter needed No Yes
Hosts Any CDN/static host, including Cloudflare Pages with zero adapter Cloudflare Workers, Node, Vercel, Netlify Functions
astro preview exercises it locally? Always Only on adapters that ship a preview entrypoint (Node, Cloudflare — proven above; not Vercel/Netlify)
Best for Content that’s the same for everyone Auth, forms, live/request-time data
  • “astro preview is a fine way to smoke-test the production build before deploying, on any adapter.” Proven above: on Vercel and Netlify specifically, it can’t run the on-demand routes at all — only the static parts. It also explicitly isn’t meant for production traffic on any adapter.
  • “Cloudflare deployment always needs adapter: cloudflare().” A fully static site deploys to Cloudflare as plain static assets with no adapter — the adapter is only for on-demand rendering on that same platform.
  • “@astrojs/db is just less popular than Drizzle, not actually deprecated.” Proven above with the real npm deprecation notice on the package itself.
  • “An Astro 6 → 7 upgrade mostly means fixing your own code.” Most of the checklist above is deleting things that no longer exist (experimental flags, astro:transitions constants) rather than fixing bugs in your code.
A fully static Astro site can be deployed where?
When is an adapter required for deployment?
Your deployed site under example.com/docs/ has all its CSS and links returning 404. What is the likely cause?
What is Starlight?
Which of these is part of a typical Astro 6 → 7 upgrade?
A project deploys to Vercel with adapter: vercel(). A teammate says "I ran astro preview and the on-demand routes worked, so we're safe to ship." What is actually true, proven above?
A generated deployment guide recommends @astrojs/db for "a lightweight embedded database." What does the real npm registry say?