Skip to content

Deploy an Astro site

Publish a static Astro site on your own domain, from your computer or from GitHub Actions. You deploy with the command-line tool, or add the Evolving Edge integration and publish as part of astro build.

Build command npm run build
Output folder dist
Settings you need site, trailingSlash: 'always', build.format: 'directory'
Routing Each page is built as about/index.html and served at /about/

You need:

  • An Astro project with static output (the default). Pages rendered on demand, server islands, Actions and middleware need a server, and aren’t supported. See What doesn’t carry over.
  • An Evolving Edge account that can deploy workloads. See Getting Started.
  • A project in the portal, and its project ID (it starts with proj_).
  • A deploy token from the portal, under Workload → Deploy Tokens, with workload:write. Scope it to the project. See Deploy tokens.
  • The domain you want to serve the site on, for example www.example.com, and access to its DNS settings.
  • Node.js 22.12 or later, on Windows (x64), macOS, or Linux (x64 or arm64).

Evolving Edge serves a folder’s index.html for an address that ends in a slash, and doesn’t add .html to an address. Tell Astro to build every page as a folder, and to link to it with a trailing slash:

astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
site: 'https://www.example.com',
trailingSlash: 'always',
build: { format: 'directory' },
});

site is used for canonical URLs, your sitemap and RSS feeds. Leave base unset: the site is served from the root of the domain.

If you wrote links by hand, in a header or footer component for example, give them a trailing slash too (/about/, not /about). Astro’s setting doesn’t rewrite them.

Only the project that claimed a domain can deploy to it, so claim it before your first deploy. In the portal, or from the command line:

Terminal window
export EE_CDN_TOKEN=<your deploy token>
npx -p @evolving-edge/ee-cdn-cli ee-domain claim www.example.com --project proj_abc123 --wait

It prints a TXT record and a CNAME record to add at your DNS provider, then waits until the domain is active. See Custom domains for the portal steps, root domains and Cloudflare.

Terminal window
npm run build
npx @evolving-edge/ee-cdn-cli dist --domain www.example.com --project proj_abc123 --trailing-slash
  • --trailing-slash makes /about redirect to /about/, so links from elsewhere that leave the slash off still arrive.
  • ee-deploy finds dist by itself if you leave the folder out.
  • Each deploy replaces the whole site on that domain. Allow up to about 90 seconds for a new deploy to reach every node.

The same package includes an Astro integration. With it, npm run build builds and publishes in one step:

Terminal window
npm install -D @evolving-edge/ee-cdn-cli
astro.config.mjs
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
import eeCdn from '@evolving-edge/ee-cdn-cli/astro';
export default defineConfig({
site: 'https://www.example.com',
trailingSlash: 'always',
build: { format: 'directory' },
integrations: [
sitemap(),
// Keep this last: it publishes whatever is in dist/ when the build finishes.
eeCdn({ deploy: { domain: 'www.example.com', projectId: 'proj_abc123' } }),
],
});

Set EE_CDN_TOKEN in your environment and run npm run build. The integration turns on the trailing-slash redirect by default. It also refuses build.format: 'file', which would make every page a 404.

Put the integration last in integrations. An integration that writes into dist/ after the build, such as @astrojs/sitemap, has to run first, or its files aren’t published.

In your repository settings, add:

  • a secret EE_CDN_TOKEN holding your deploy token
  • variables EE_DOMAIN (for example www.example.com) and EE_CDN_PROJECT_ID

Then create .github/workflows/deploy.yml:

name: Deploy to Evolving Edge
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ee-deploy
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
- name: Deploy
env:
EE_CDN_TOKEN: ${{ secrets.EE_CDN_TOKEN }}
EE_CDN_PROJECT_ID: ${{ vars.EE_CDN_PROJECT_ID }}
run: npx --yes @evolving-edge/ee-cdn-cli dist --domain ${{ vars.EE_DOMAIN }} --trailing-slash

If you use the integration instead, drop the Deploy step and give the build step the token:

- run: npm run build
env:
EE_CDN_TOKEN: ${{ secrets.EE_CDN_TOKEN }}

Redirects. Astro’s redirects option produces HTML redirect pages in a static build. For real 301 redirects, add a _redirects file to public/, which Astro copies to the site root:

public/_redirects
/old-post/ /blog/new-post/ 301
/docs/* /guides/:splat 301

Rules can be 301 or 302, and * and :splat wildcards work. Redirects apply to public (Level 0) sites.

404 page. Add src/pages/404.astro. Astro builds it to dist/404.html, and Evolving Edge serves it, with a 404 status, for any address that doesn’t exist. Some starter templates, including the blog template, don’t include one. See Custom 404 pages.

Caching. Nothing to set up. Astro puts its hashed assets in _astro/, which is cached for a year by default. HTML pages are checked on every request, so a new deploy shows as soon as it reaches the nodes. Everything else is cached for an hour. See Caching.

  • Rendering on demand. Routes with export const prerender = false, server islands, Astro Actions, sessions and middleware need a server at request time. Evolving Edge serves static output only, so build these pages at build time or leave them out.
  • Adapters. No adapter is needed. Remove any host adapter (Netlify, Vercel, Node) from your config.
  • Subfolder deploys. A site owns its whole domain, so base stays unset.
  • Deploy previews for each pull request. To preview, deploy to a second domain.
What you see Why What to do
Every page except the home page is a 404 build.format is 'file', so Astro built about.html Set build.format: 'directory'
/about is a 404 but /about/ works The trailing-slash redirect isn’t on Deploy with --trailing-slash
The sitemap is missing from the published site The integration runs before @astrojs/sitemap Move eeCdn() to the end of integrations
Canonical and feed URLs point at localhost site isn’t set Set site to your domain
The deploy is refused for your domain The domain isn’t claimed for this project Claim it first
No deploy token EE_CDN_TOKEN isn’t set Export it, or add the repository secret
An authorisation error on upload The token is scoped to a project and no project was given Pass --project (or projectId to the integration)

More in the command line troubleshooting.