EN
guide

Deploying

Ship to Node, Cloudflare Pages, Netlify, or Vercel — and how the contact form's server action works on otherwise-static hosts.

Last updated

Where you deploy depends on one question: does the site need server compute?

  • The docs template is fully static (output: 'static', no adapter, no server code). astro build emits dist/ — drop it on any static host.
  • The starter ships a contact form built on Astro Actions, which run server-side. It pins the @astrojs/node adapter so that endpoint has somewhere to run. To deploy elsewhere, you swap the adapter to match the host.

Static hosts (docs, or starter without the form)

If there’s no server code, the build is portable:

terminal bash
pnpm build   # → dist/

Point Cloudflare Pages, Netlify, GitHub Pages, or any CDN at dist/. Build command pnpm build, output directory dist. Done.

Node

The starter’s default. Build, then run the standalone server entry:

terminal bash
pnpm build
node ./dist/server/entry.mjs

Put it behind a reverse proxy (Caddy, nginx) or in a container. Set the email env vars (see Contact form & email) in the runtime environment.

Netlify / Vercel

To deploy the starter (with its server action) to one of these, swap the adapter. The change is two lines plus the dependency:

astro.config.mjs
import { defineConfig } from 'astro/config';
import netlify from '@astrojs/netlify'; // or @astrojs/vercel

export default defineConfig({
output: 'static',
adapter: netlify(),
// …the rest is unchanged
});
terminal bash
pnpm add @astrojs/netlify   # or @astrojs/vercel

The adapter turns the action endpoint into a serverless / edge function at build time. The static pages still deploy to the CDN; the function handles the form POST. The same applies to Cloudflare Workers with @astrojs/cloudflare — but not to Cloudflare Pages; see below.

Cloudflare Pages

Don’t reach for @astrojs/cloudflare here — the current adapter emits a Worker build that Pages can’t run. The pattern that works (and the one astro-ignite’s own marketing site ships with) is a fully static build plus a hand-written Pages Function:

  1. Keep output: 'static' and no adapter. pnpm build emits plain dist/; Pages serves it from the CDN.
  2. Write the form handler as a Pages Function at functions/api/contact.ts — an onRequestPost that validates the fields, checks the honeypot, and talks to your email provider’s HTTP API, reading secrets from the function’s env binding (set them as encrypted variables on the Pages project).
  3. Repoint the contact <form> at /api/contact and drop the Astro Action. Cloudflare deploys the functions/ directory alongside dist/ automatically.

For a complete, copyable handler, see apps/site/functions/api/contact.ts in the astro-ignite repo — that file is exactly this pattern in production.

The contact form on a purely static host

If you want a static-only deploy (no functions at all), the server action can’t run. Two clean options:

  1. Use the host’s function layer directly. On Netlify and Vercel the adapter compiles the action into a serverless function — no extra work. On Cloudflare Pages, write the small Pages Function by hand (see above). Either way the form stays server-side, owned by your code.
  2. Hand the form off. Repoint the <form> at a third-party form endpoint (Formspree, Web3Forms, your own webhook) and drop the action. You lose the typed Zod validation and the honeypot, but the deploy is pure static.

Before you ship

  • Set siteConfig.url to your production origin — it drives canonical URLs, the sitemap, OG tags, and robots.txt.
  • Set the email + analytics env vars in the host’s dashboard, not in the repo.
  • Re-run pnpm build locally first; it surfaces schema and type errors the host would otherwise fail on.