To deploy Astro, run npm run build and publish the dist/ folder to any static host. Cloudflare Pages, Netlify and Vercel detect Astro automatically when you connect a Git repository; you confirm the build command (npm run build) and output directory (dist), and every push deploys. You only need an adapter if some pages render on request.
This guide compares the major hosts, gives the exact settings for each, and covers environment variables, custom domains and the mistakes that cause most failed deploys.
Key Takeaways
- A static Astro site is a folder of files. Any host that serves files can serve it.
- Build command
npm run build, output directorydist, Node.js 22.12 or newer. Astro 7 won’t build on older Node.- Install an adapter (
npx astro add cloudflare,netlify,vercelornode) only for on-demand pages, server islands, actions or sessions.- GitHub Pages project sites need
siteandbaseset inastro.config.mjs.- Use
astro:envto declare environment variables with types and validation.
What you’re deploying
Astro has two output shapes:
- Static (default).
astro buildwrites HTML, CSS, JS and assets todist/. Upload that folder anywhere. - Static plus on-demand. With an adapter, Astro also builds a server entry for pages that render per request. The host runs it as a function or worker, and serves the static pages as files.
Start static. Most marketing sites, blogs, docs and portfolios never need anything else, and a static deploy has the fewest moving parts. The Astro SSR guide covers when the second shape is worth it.
The build, step by step
Locally, before connecting any host:
npm ci
npm run build
npx astro preview # serves dist/ at http://localhost:4321
If npm run build succeeds locally and astro preview looks right, the host will almost certainly succeed too. Most failed deploys come from a difference between the two environments: Node version, a missing environment variable, or a file whose name differs only in case (macOS doesn’t care, Linux build servers do).
Pin the Node version so every environment agrees. Add an .nvmrc file:
22
and an engines field in package.json:
{
"engines": { "node": ">=22.12.0" }
}
Host comparison
| Cloudflare Pages | Netlify | Vercel | GitHub Pages | |
|---|---|---|---|---|
| Detects Astro | Yes (preset) | Yes | Yes | Via Actions workflow |
| Static hosting | Yes | Yes | Yes | Yes |
| On-demand adapter | @astrojs/cloudflare |
@astrojs/netlify |
@astrojs/vercel |
No |
| Preview deploys per branch/PR | Yes | Yes | Yes | No |
_redirects / _headers files |
Yes | Yes | No (use vercel.json) |
No |
| Custom domain + HTTPS | Yes | Yes | Yes | Yes |
All four have free tiers that comfortably cover a typical static site; check each provider’s current pricing page for limits on builds, bandwidth and function invocations.
How to choose:
- Cloudflare Pages if you want a large global network and may later use Workers, KV or R2 alongside your site.
- Netlify if you want built-in forms, split testing and a mature UI with
_redirectssupport. - Vercel if your team already uses it for other projects.
- GitHub Pages for open-source docs or personal sites that live with their code and need nothing server-side.
Deploy to Cloudflare Pages
- Push your project to GitHub or GitLab.
- In the Cloudflare dashboard, open Workers & Pages, create an application, choose Pages, and connect the repository.
- Select the Astro framework preset. It fills in:
- Build command:
npm run build - Build output directory:
dist
- Build command:
- Under environment variables, add
NODE_VERSION=22if the build image defaults to an older Node. - Save and deploy.
To deploy from your machine or CI without Git integration, use Wrangler:
npm run build
npx wrangler pages deploy dist --project-name my-site
Put _redirects and _headers files in public/ and Cloudflare applies them. The Cloudflare deploy docs for Komal themes walk through this with screenshots, and Cloudflare’s own Astro guide covers on-demand rendering with the adapter.
Deploy to Netlify
- In Netlify, choose Add new site and import from Git.
- Netlify detects Astro and sets:
- Build command:
npm run build - Publish directory:
dist
- Build command:
- Deploy.
Or commit a netlify.toml so settings live with the code:
[build]
command = "npm run build"
publish = "dist"
[build.environment]
NODE_VERSION = "22"
For on-demand pages, npx astro add netlify installs the adapter and Netlify runs those routes as functions.
Deploy to Vercel
- Import the repository in Vercel.
- Framework preset: Astro. Build command and output directory are filled in.
- Set the Node.js version to 22.x in project settings if needed.
- Deploy.
Or from the CLI:
npx vercel # preview
npx vercel --prod # production
For on-demand routes, add the adapter with npx astro add vercel.
Deploy to GitHub Pages
GitHub Pages serves static files from a branch or an Actions artifact. Astro publishes an official action.
First, tell Astro where the site lives:
// astro.config.mjs
export default defineConfig({
site: 'https://username.github.io',
base: '/my-repo', // omit for a username.github.io repo or a custom domain
});
Then add the workflow:
# .github/workflows/deploy.yml
name: Deploy to GitHub Pages
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: withastro/action@v3
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
In the repository settings, set Pages > Source to GitHub Actions. Check the action versions against the Astro GitHub Pages guide when you set this up, since they change over time.
With a base, every hard-coded link must include it. Build links with import.meta.env.BASE_URL instead of starting them with /.
Every Komal theme is a static Astro 7 project that builds to dist/ with no adapter, so it deploys to any of these hosts unchanged. Browse Astro themes, or read the static hosting docs for Netlify, Vercel and GitHub Pages specifics.
Other hosts
Because the output is plain files, anything that serves static files works: AWS S3 with CloudFront, Azure Static Web Apps, Render, Firebase Hosting, a VPS running nginx or Caddy. Upload dist/ and configure the server to serve 404.html for missing paths.
For on-demand rendering on your own server, use the Node adapter:
npx astro add node
npm run build
node ./dist/server/entry.mjs
Environment variables with astro:env
Declare variables in config so Astro validates them at build or start time:
// astro.config.mjs
import { defineConfig, envField } from 'astro/config';
export default defineConfig({
env: {
schema: {
PUBLIC_SITE_NAME: envField.string({ context: 'client', access: 'public', default: 'My Site' }),
NEWSLETTER_API_KEY: envField.string({ context: 'server', access: 'secret' }),
},
},
});
Then import them from typed modules:
import { PUBLIC_SITE_NAME } from 'astro:env/client';
import { NEWSLETTER_API_KEY } from 'astro:env/server';
The rules:
client+publicvalues are inlined into client code. Never put secrets there.server+secretvalues are read at runtime on the server and never bundled.- Locally, values come from
.env; on the host, set them in the dashboard.
A missing required variable fails with a clear message instead of rendering undefined. The environment variables guide covers import.meta.env for simpler cases.
Custom domains and HTTPS
Every host above issues HTTPS certificates automatically. The steps are the same everywhere: add the domain in the host’s dashboard, create the DNS records it shows you, wait for verification. After that, update site in astro.config.mjs to the new domain and redeploy so canonicals and the sitemap use it.
The custom domain tip covers apex vs www and the redirect between them, and the custom domains docs show the DNS records for each host.
Build speed
Small sites build in seconds. Builds slow down with many images (each one is processed) and with large content sources. Hosts cache node_modules between builds; caching Astro’s image cache as well helps most. See faster Astro builds in CI for the exact directories to cache.
Preview deploys and staging
Cloudflare Pages, Netlify and Vercel build every branch or pull request to its own URL. Use them:
- Review content before it’s live. Writers share a preview link instead of screenshots.
- Catch build failures early. A failing preview blocks the merge, not the production deploy.
- Keep previews out of search. Preview URLs are public. Add a
noindexheader for non-production hosts, or generaterobots.txtfrom an environment variable so only production allows crawling. The robots.txt tip shows how.
Set environment variables per environment in the host dashboard. A preview should never use production API keys that write data.
Rollbacks
Every host here keeps previous deploys. If a release breaks something:
- Cloudflare Pages: Deployments → choose a previous production deploy → Rollback.
- Netlify: Deploys → choose a deploy → Publish deploy.
- Vercel: Deployments → choose a deploy → Promote (or Instant Rollback).
- GitHub Pages: re-run the workflow for an earlier commit, or revert and push.
Rollbacks are instant on the first three because the old build is still stored. That’s one more reason to deploy static output: there’s no database migration to undo.
Troubleshooting failed deploys
| Symptom | Likely cause | Fix |
|---|---|---|
Node.js vX is not supported |
Host uses an older Node | Set Node 22 via .nvmrc, NODE_VERSION or host settings |
Works locally, Cannot find module on host |
File name case differs from import | Match the import’s case to the file exactly |
| Blank page, assets 404 on GitHub Pages | base missing or links ignore it |
Set base, build links with import.meta.env.BASE_URL |
Missing environment variable |
Variable not set on the host | Add it in the host dashboard for that environment |
| Every route shows the home page | Host configured as a single-page app | Remove the SPA rewrite, make sure 404.html exists |
| On-demand route returns 404 | No adapter, or page lacks prerender = false |
Install the adapter, check the page export |
| Old content after deploy | CDN or browser cache | Purge the CDN cache, check Cache-Control headers |
When the log is long, scroll to the first error. Later errors are usually consequences of it.
Deploying from CI instead of the host
Sometimes you want builds in your own CI (GitHub Actions, GitLab CI) and the host only to serve files: to run tests first, to build once and deploy to several places, or to keep build secrets out of the host. Build in CI, then upload dist/ with the host’s CLI:
npm ci
npx astro check
npm run build
npx wrangler pages deploy dist --project-name my-site # Cloudflare Pages
# npx netlify deploy --dir=dist --prod # Netlify
# npx vercel deploy --prebuilt --prod # Vercel (after `vercel build`)
Store the host’s API token as a CI secret. You lose the host’s automatic preview comments on pull requests unless you add them to your workflow, so most teams start with the host’s Git integration and move to CI deploys only when they need the control.
Deploy checklist
-
npm run buildandnpx astro previewwork locally - Node 22.12+ pinned with
.nvmrcor a host setting -
siteset to the production URL (andbasefor GitHub Pages project sites) - Environment variables declared with
astro:envand set on the host - Adapter installed only if you have on-demand routes
-
404.astroexists - Redirects for any moved URLs
- Custom domain verified, HTTPS active, one canonical host (
wwwor apex) - Sitemap submitted in Search Console
FAQ
Where does Astro put the built site?
In dist/ at the project root. Change it with the outDir option in astro.config.mjs.
Do I need an adapter to deploy Astro?
Not for static sites. You need one only for pages that render on request, server islands, actions, sessions or middleware that runs per request.
Why does my Astro build fail on the host but work locally?
The usual causes are an older Node version on the host (Astro 7 needs 22.12+), a missing environment variable, or a file path that differs in letter case. Check the first error in the build log.
Can I deploy Astro to GitHub Pages?
Yes. Set site and base in config, add the withastro/action workflow, and set the Pages source to GitHub Actions.
What is the best host for Astro?
For a static site, Cloudflare Pages, Netlify and Vercel are all strong and free to start. Pick based on the extras you need (forms, edge storage, existing team accounts) rather than raw speed.