Deployment & Ecosystem
The production build
Section titled “The production build”astro build produces your deployable output in dist/. What ends up there depends on your rendering mode:
- Static —
dist/is a folder of.htmlfiles, 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.
Two deploy targets
Section titled “Two deploy targets”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"]
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’s real limits
Section titled “astro preview’s real limits”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.
Set site and base correctly
Section titled “Set site and base correctly”Two config values bite people at deploy time:
sitemust be your real deployed URL (e.g.https://example.com). Sitemaps, canonical tags, and RSS all build absolute URLs from it — wrongsite, wrong links.basemust match the subpath you’re served from. Deploy toexample.com/docs/and you setbase: '/docs', then reference internal links with the base prefix. Serve at the domain root and you omit it. A mismatchedbaseis the usual cause of “the deployed site’s CSS and links are all 404” — the assets are looked up at the wrong path.
The ecosystem
Section titled “The ecosystem”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 deprecatedreturns: “This package is deprecated. Use a database client (Drizzle, Kysely, etc.) directly instead.” Reach fornode:sqlite, Drizzle, or a hosted database (Turso, Neon, PlanetScale) instead.astro-integration-kit— also npm-deprecated (npm view astro-integration-kit deprecatedpoints 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.
Upgrading from Astro 6 to 7
Section titled “Upgrading from Astro 6 to 7”Most projects upgrade in an afternoon. The checklist:
- Node 22.12+ is the floor (unchanged from Astro 6). Run
npx @astrojs/upgradeto bumpastroand every@astrojs/*integration together. - Remove the stabilised experimental flags:
experimental.cache,experimental.routeRules,experimental.loggermove to top-levelcache,routeRules,logger;experimental.rustCompiler,queuedRendering,advancedRoutingare deleted (now default). - Fix what the Rust compiler now rejects: unclosed tags and invalid nesting are build errors. The error messages point at the file and line.
- Check whitespace:
compressHTMLdefaults to'jsx'. Add{" "}where inline elements need a space, or setcompressHTML: truetemporarily. - Markdown plugins: if you use remark/rehype plugins, install
@astrojs/markdown-remarkand setmarkdown: { processor: unified() }; otherwise enjoy Sätteri. - Vite 8: review the Vite 8 migration notes for any Vite-specific plugin you configured under the
vitekey. - Rename
src/fetch.tsif you had an unrelated file by that name (or setfetchFile). - Removed APIs:
@astrojs/db(switch tonode:sqlite, Drizzle, or a hosted DB), theastro:transitionsevent constants (use the string event names such as'astro:before-preparation'), andgetContainerRenderernow imported from a framework-specific subpath (@astrojs/preact/container-rendererif 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.
Reading AI-generated code
Section titled “Reading AI-generated code”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/dborastro-integration-kitwithout a caveat? Both are npm-deprecated — proven above with their real deprecation notices, not a guess about their popularity. - Does it claim
astro previewis a valid lightweight production server? Proven above with the CLI’s own words: “not designed to be run in production.” - Does it assume
astro previewexercises on-demand routes the same way regardless of adapter? Proven above: only Node and Cloudflare ship a preview entrypoint; Vercel and Netlify don’t, soastro previewonly 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
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.
Mental Model
Section titled “Mental Model”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.
Trade-offs
Section titled “Trade-offs”| 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 |
Common Misconceptions
Section titled “Common Misconceptions”- “
astro previewis 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/dbis 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 (
experimentalflags,astro:transitionsconstants) rather than fixing bugs in your code.