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/ |
Before you begin
Section titled “Before you begin”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).
1. Configure Astro
Section titled “1. Configure Astro”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:
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.
2. Claim your domain
Section titled “2. Claim your domain”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:
export EE_CDN_TOKEN=<your deploy token>npx -p @evolving-edge/ee-cdn-cli ee-domain claim www.example.com --project proj_abc123 --waitIt 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.
3. Deploy from your computer
Section titled “3. Deploy from your computer”npm run buildnpx @evolving-edge/ee-cdn-cli dist --domain www.example.com --project proj_abc123 --trailing-slash--trailing-slashmakes/aboutredirect to/about/, so links from elsewhere that leave the slash off still arrive.ee-deployfindsdistby 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.
Or publish as part of the build
Section titled “Or publish as part of the build”The same package includes an Astro integration. With it, npm run build builds and publishes in one step:
npm install -D @evolving-edge/ee-cdn-cliimport { 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.
4. Deploy from GitHub Actions
Section titled “4. Deploy from GitHub Actions”In your repository settings, add:
- a secret
EE_CDN_TOKENholding your deploy token - variables
EE_DOMAIN(for examplewww.example.com) andEE_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-slashIf 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, 404 page and caching
Section titled “Redirects, 404 page and caching”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:
/old-post/ /blog/new-post/ 301/docs/* /guides/:splat 301Rules 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.
What doesn’t carry over
Section titled “What doesn’t carry over”- 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
basestays unset. - Deploy previews for each pull request. To preview, deploy to a second domain.
Troubleshooting
Section titled “Troubleshooting”| 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.
