Static Hosting (Netlify, Vercel, Pages)
Astro’s default output is a folder of static HTML, CSS, and assets — exactly what static hosts are built to serve. Because Astro ships zero JavaScript by default and only hydrates the islands you mark with client:* directives, a static build is small, fast, and cacheable at the edge. This page walks through deploying that dist/ output to the four most popular static hosts and the configuration each one expects.
How static builds work
When you run the build with no SSR adapter configured, Astro renders every page at build time and writes the result to dist/. Your job at deploy time is simply to point a host at that directory and tell it the build command.
npm run build
Output:
12:01:14 [build] 18 page(s) built in 1.42s
12:01:14 [build] Complete!
dist/
├── index.html
├── about/index.html
├── _astro/index.Bq3kP9.css
└── favicon.svg
Every host below uses the same two settings: a build command (npm run build) and a publish/output directory (dist).
| Host | Build command | Publish dir | Config file |
|---|---|---|---|
| Netlify | npm run build | dist | netlify.toml |
| Vercel | npm run build | dist | auto-detected |
| Cloudflare Pages | npm run build | dist | dashboard / wrangler.toml |
| GitHub Pages | npm run build | dist | GitHub Action |
Netlify
Netlify auto-detects Astro, but committing a netlify.toml makes the configuration explicit and version-controlled.
# netlify.toml
[build]
command = "npm run build"
publish = "dist"
[build.environment]
NODE_VERSION = "20"
[[redirects]]
from = "/old-blog/*"
to = "/blog/:splat"
status = 301
Connect the repository in the Netlify dashboard, or push from the CLI:
npm i -g netlify-cli
netlify deploy --build --prod
Netlify only reads
_redirectsand_headersfiles from your publish directory. Put them inpublic/so Astro copies them verbatim intodist/.
Vercel
Vercel detects Astro from package.json and needs no config for a basic static deploy. For a static-only build, make sure you are not importing @astrojs/vercel as an adapter — leaving output unset (or 'static') keeps the build fully static.
npm i -g vercel
vercel --prod
If you want a committed config, a minimal vercel.json pins the directories:
// vercel.json
{
"buildCommand": "npm run build",
"outputDirectory": "dist",
"cleanUrls": true
}
cleanUrls serves /about instead of /about.html, matching Astro’s default directory-style output.
Cloudflare Pages
In the Cloudflare dashboard, create a Pages project, connect your Git provider, and set the framework preset to Astro. The build command is npm run build and the output directory is dist. Set NODE_VERSION to 20 (or newer) under environment variables so the build matches your local toolchain.
For redirects and headers, drop _redirects and _headers files into public/:
# public/_redirects
/docs/* /guides/:splat 301
Cloudflare Pages caps
_redirectsat 2000 static rules. For anything dynamic, reach for an SSR adapter instead — see the SSR deployment page.
GitHub Pages
GitHub Pages needs two things: a correct site (and base if you deploy to a project subpath) in astro.config.mjs, and a workflow that builds and publishes.
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
site: 'https://yourname.github.io',
base: '/my-repo',
});
Use Astro’s official action, which handles building and uploading the Pages artifact:
// .github/workflows/deploy.yml
name: Deploy to GitHub Pages
on:
push:
branches: [main]
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
Then enable Settings → Pages → Source → GitHub Actions in the repository. Because internal links must respect base, always build hrefs with import.meta.env.BASE_URL:
---
const base = import.meta.env.BASE_URL;
---
<a href={`${base}/about`}>About</a>
Best practices
- Pin
NODE_VERSION(20+) on every host so CI builds match your local Astro toolchain. - Keep
_redirectsand_headersinpublic/so they land indist/untouched — never hand-editdist/. - Set
siteinastro.config.mjsso sitemaps, canonical URLs, and RSS feeds resolve absolute links correctly. - Lean on zero-JS-by-default: only add
client:*directives to islands that truly need interactivity, keeping payloads tiny on the edge. - Use immutable, hashed asset filenames (Astro does this for
_astro/*) and let the host cache them aggressively. - If you later need request-time logic, switch to an SSR adapter rather than hacking around static limits.