Skip to content

Deploy a Hugo site

Publish a Hugo site on your own domain, from your computer or from GitHub Actions.

Build command hugo build --gc --minify
Output folder public
Settings you need baseURL set to your domain
Routing Hugo’s default pretty URLs (/about/ → about/index.html) work as they are

You need:

  • A Hugo site that builds on your computer with hugo build.
  • 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), to deploy from your computer. The deploy tool is an npm package; Hugo itself doesn’t need Node.js, and the GitHub Action below brings its own.

Hugo writes absolute URLs into your sitemap, RSS feeds and canonical links from baseURL, so set it to your real domain, with a trailing slash:

hugo.toml
baseURL = "https://www.example.com/"

Your site is served from the root of the domain. If you’re moving from a GitHub Pages project site (https://you.github.io/repo/), drop the /repo/ path.

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.

Build the site, then deploy the public folder:

Terminal window
hugo build --gc --minify
npx @evolving-edge/ee-cdn-cli public --domain www.example.com --project proj_abc123 --trailing-slash
  • --trailing-slash makes /about redirect to /about/, where Hugo put the page. Hugo’s own links already end in a slash; this catches links typed or copied without one.
  • ee-deploy finds public 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.

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
with:
submodules: recursive # if your theme is a Git submodule
fetch-depth: 0 # for .GitInfo and .Lastmod
- uses: peaceiris/actions-hugo@v3
with:
hugo-version: '0.167.0'
extended: true
- name: Build
run: hugo build --gc --minify --baseURL "https://${{ vars.EE_DOMAIN }}/"
- name: Deploy
uses: evolving-edge/ee-cdn-cli@v0.4.0
with:
directory: public
domain: ${{ vars.EE_DOMAIN }}
project: ${{ vars.EE_CDN_PROJECT_ID }}
token: ${{ secrets.EE_CDN_TOKEN }}
trailing-slash: true

The Action brings its own Node.js, so the workflow needs no setup-node step. If your site uses Dart Sass or npm packages, add those steps before Build, as in Hugo’s own GitHub Pages workflow. To keep a new release of the packaging tool from changing your deploys, pin it with builder-version; see The GitHub Action.

Redirects. Put a _redirects file in static/; Hugo copies it to the site root:

static/_redirects
/old-post/ /posts/new-post/ 301
/blog/* /posts/:splat 301

Rules can be 301 or 302, and * and :splat wildcards work. Redirects apply to public (Level 0) sites. Hugo’s own aliases keep working too, as HTML redirect pages.

404 page. A layouts/404.html template builds public/404.html. Evolving Edge serves it, with a 404 status, for any address that doesn’t exist. Most themes include one. See Custom 404 pages.

Caching. HTML pages are checked on every request, so a new deploy shows as soon as it reaches the nodes. Other files are cached for an hour. Hugo mixes fingerprinted files with the files static/ copies, so no folder is safe to cache for a year by default. To cache fingerprinted files for a year, keep them in a folder of their own and add the _headers rule in Caching hashed assets for a year.

  • Subfolder deploys. A site owns its whole domain; there’s no equivalent of a GitHub Pages project path.
  • hugo deploy. Its targets are cloud storage buckets. Use ee-deploy or the Action instead.
  • Deploy previews for each pull request. To preview, deploy to a second domain.
What you see Why What to do
/about is a 404 but /about/ works The trailing-slash redirect isn’t on Deploy with --trailing-slash (or trailing-slash: true in the Action)
Sitemap or RSS links point at localhost or example.org baseURL wasn’t set for the production build Set it in hugo.toml, or pass --baseURL as in the workflow
Pages load without styles after moving from GitHub Pages baseURL still includes the old /repo/ path Set baseURL to the domain root
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 set EE_CDN_PROJECT_ID)

More in the command line troubleshooting.