Build a Manual with Hugo #4: Deploying to Cloudflare Pages and Connecting a Domain

4 min read

By the time you reach #3, you have docs that look good locally. But localhost is something only you can see. In this post we send these docs out so anyone can reach them by address.

This series, Build a Manual with Hugo, runs in six parts.

  • #1: From Install to Your First Doc
  • #2: Sidebar and Search — Shaping the Information Architecture
  • #3: Writing Content — Code Blocks, Mermaid, Callouts
  • #4: Deploying to Cloudflare Pages and Connecting a Domain ← this post
  • #5: Multilingual Content and Versioning
  • #6: Maintenance — Search Index, Accessibility, Documentation Culture

This post covers pushing to GitHub, connecting to Cloudflare Pages and setting up the build, and attaching a custom domain.

Where Do You Host a Static Site? #

What Hugo produces is a bundle of static HTML, CSS, and JS files. There’s no server-side code running, so any hosting that just serves files as-is is enough. In this post we use Cloudflare Pages. The free tier is generous, it automatically builds and deploys when you push to a Git repository, and a global CDN and HTTPS come built in.

1. Pushing to GitHub #

Cloudflare Pages looks at a Git repository and builds from it. First, push your docs to GitHub. So that the build output and cache don’t go into the repository, start by creating a .gitignore.

.gitignore
/public/
/resources/_gen/
.hugo_build.lock

Then initialize the repository and push.

Push to GitHub
git init
git add .
git commit -m "docs: first doc"
git branch -M main
git remote add origin https://github.com/your-account/my-docs.git
git push -u origin main

2. Connecting to Cloudflare Pages #

In the Cloudflare dashboard, go to Workers & Pages → Create → Pages → Connect to Git and select the repository you just pushed. Then specify the build settings as follows.

ItemValue
Framework presetHugo
Build commandhugo --gc --minify
Build output directorypublic

Press Save and Deploy and the first deployment starts; when it finishes, you get a project-name.pages.dev address. From then on, it rebuilds automatically every time you push to main.

3. Build Environment — Hugo Version and Go #

When a deployment fails on the first try, nine times out of ten it’s a build environment issue. Check two things.

First, the Hugo version. Set HUGO_VERSION in the environment variables to match the same version you use locally. Themes that use built-in asset processing, like Hextra, need the Extended version, and Cloudflare installs the Extended build of the version you specify with HUGO_VERSION.

Environment variableValue (example)
HUGO_VERSION0.140.0

Second, Go. As we saw in #1, Hextra is distributed as a Hugo Module, and downloading the module requires Go. The Cloudflare build image includes Go, so it usually works as-is, but if the build stalls because it can’t fetch the module, adding a GO_VERSION environment variable to pin the version resolves it.

4. Connecting a Custom Domain #

Instead of the pages.dev address, attach a domain you bought yourself. In the Pages project’s Custom domains → Set up a domain, enter the domain.

If the domain is already managed in Cloudflare, the DNS records are added automatically. If it’s managed elsewhere, add the following record directly in that DNS.

DNS record (subdomain)
Type     Name     Value
CNAME    docs     project-name.pages.dev

To attach it directly to the root domain (example.com), using Cloudflare DNS, which supports CNAME flattening, is the cleanest approach. Once the connection is done, Cloudflare automatically issues and renews the HTTPS certificate.

One Trap — Publish Dates and Scheduling #

Once you’re deployed, there’s a problem you’ll run into at least once: a post written with a future date doesn’t show up even when that day arrives.

By default, Hugo excludes future-dated posts from the build. And Cloudflare Pages rebuilds only when there’s a push. Put the two together, and a post scheduled with a future date doesn’t go public on its own when the date arrives, because no new build runs that day. If you absolutely need scheduled publishing, you have to set up a mechanism that triggers a build once a day (a scheduled deploy hook). If that’s too much overhead, publishing each post with that day’s date as you write it is simpler and avoids accidents.

Wrapping Up #

In this post we pushed the docs to GitHub, connected to Cloudflare Pages and set up the build, and attached a custom domain. Now the docs have a real address and live on the internet.

The next post covers multilingual content and versioning. We’ll cover serving one set of docs in several languages, and how to keep old docs around together when the product version goes up.

X