Islands and deployment

Deploy a static Astro site

Upload the build output to a static host and verify the real production URL, redirects, and fallback behavior.

A static Astro site is a folder of files, so deploying it is easy. Every static host needs the same two settings:

Build command: npm run build
Publish directory: dist

Connect the Git repository to the host, enter those two values, and push. The host installs dependencies, runs the build, and serves dist/. Cloudflare Pages, Netlify, Vercel, and GitHub Pages all work this way.

You don’t need an adapter for this. Add one only when you have on-demand routes, or when a platform feature requires it.

Set the production URL

Astro needs to know the final address whenever it generates an absolute URL: sitemap entries, canonical tags, RSS links. Set it in astro.config.mjs:

import { defineConfig } from 'astro/config'

export default defineConfig({
  site: 'https://notes.flaviocopes.com'
})

Without it, those URLs come out relative or wrong, and you notice only when a feed reader or a search engine complains.

Environment variables live in two places

Your .env file is not committed, so the host doesn’t have it. Any variable the build reads, like the GITHUB_TOKEN from the data module, must be added in the host’s build settings too.

This is the most common “works on my machine” failure. The local build passes. The production build reads import.meta.env.GITHUB_TOKEN, gets undefined, and the GitHub request fails with a 401. The fix is one variable in the dashboard, not a code change.

Runtime secrets are a different thing. They matter only when a deployed server function reads them at request time. A fully static site has none.

One more gotcha: hosts often default to an older Node.js than the one on your laptop. If the build fails on syntax your machine accepts, pin the version in the host settings or in a .node-version file.

Test the real domain

The deployment is done when the real URL behaves, not when the dashboard shows a green check. Use curl to see the status codes:

curl -I https://notes.flaviocopes.com/notes/scoped-styles/
curl -I https://notes.flaviocopes.com/does-not-exist/
curl -I https://notes.flaviocopes.com/old-path/

You want 200 for the note, 404 for the unknown route, and a 301 with a Location header for the redirect. Then load one optimized image and one endpoint and check their status codes too.

Read the response headers while you’re there. Caching and redirect behavior come from the host as much as from your code, and preview never showed them to you.

Finally, open one island on the production site with the network throttled, then with JavaScript disabled. The production Network panel, not your source tree, shows what a visitor pays for.

Lesson completed