Hugo Base URL with Cloudflare Pages
Table of Contents
Introduction
I’ve been using Cloudflare Pages for a while now, after migrating from GitHub pages. The limits are more generous, the preview deployments are super handy, and I was already using Cloudflare.
After recently redoing my homepage, I realized that a lot of links and images would be wrong when making a pull request. They were pointing to the live site rather than the preview deployment for that pull request.
Problem
The problem lies with the Hugo
baseURL
setting.
When absolute URLs are created in templates, the path is appended to the end
of the base URL. As Cloudflare Pages uses a new domain name for every deployment,
this is not a static value.
Solution
While I would love to just set the base URL to be “/” and be done,
some places like the contents of the sitemap or
canonical URL
MUST be an absolute URL (https://example.com/page
rather than /page
).
My first recommendation is to convert as many links as possible to relative URLs rather than absolute URLs. This solves most of the problems. If you’re using a third party theme, you may be able to create some patches to help. I like this package a lot. More about that here.
absURL
->relURL
absLangURL
->relLangURL
.Permalink
->.RelPermalink
What I also ended up doing was to leave the baseURL
setting to “/”,
so when developing locally everything worked. I then made a quick script that
checked if the build was running on a deployment build, or the main branch.
If running on a deployment build, it would use the value from the CF_PAGES_URL
environment variable, which gives the full URL of the deployment. If the build
is running on the main branch, I hardcoded the URL, since the CF_PAGES_URL
environment variable always gives the <commit>.<project>.pages.dev
domain, even if
you have a custom domain setup.
This culminates to this:
package.json
1{
2 "dependencies": {
3 "cross-spawn": "^7.0.3",
4 "hugo-extended": "^0.111.3"
5 },
6 "scripts": {
7 "build": "node build.js",
8 "postinstall": "patch-package"
9 }
10}
build.js
1const spawn = require("cross-spawn");
2const fs = require("fs");
3var base_url = "/";
4
5if (process.env.CF_PAGES_BRANCH === "main") {
6 base_url = "https://blog.nathanv.me/";
7} else if (process.env.CF_PAGES_URL) {
8 base_url = process.env.CF_PAGES_URL;
9}
10
11console.log(`Using base url "${base_url}"`);
12cmd = spawn.sync(
13 "hugo",
14 ["--cleanDestinationDir", "--minify", "-b", base_url],
15 { encoding: "utf8" }
16);
17
18if (cmd.error) {
19 console.log("ERROR: ", cmd.error);
20}
21
22console.log(cmd.stdout);
23console.error(cmd.stderr);
24
25process.exit(cmd.status);
While not pretty, it works mostly. If you have more than one custom domain
(like a www
version), that one will just exclusively point to the custom domain
you choose as “most” canonical.
Environment | Works |
---|---|
Latest Main (custom domain) | Yes |
Alternative Latest Main (www.custom domain) | Kinda |
Older Main (<commit>.<project>.pages.dev) | No |
Preview (<commit>.<project>.pages.dev) | Yes |
Locally | Yes |