Skip to content
Astro as deployment 3 min read

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).

HostBuild commandPublish dirConfig file
Netlifynpm run builddistnetlify.toml
Vercelnpm run builddistauto-detected
Cloudflare Pagesnpm run builddistdashboard / wrangler.toml
GitHub Pagesnpm run builddistGitHub 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 _redirects and _headers files from your publish directory. Put them in public/ so Astro copies them verbatim into dist/.

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 _redirects at 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 _redirects and _headers in public/ so they land in dist/ untouched — never hand-edit dist/.
  • Set site in astro.config.mjs so 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.
Last updated June 14, 2026
Was this helpful?