How to Host a Personal Blog with Hugo on Cloudflare Pages
Introduction
How to Host a Personal Blog with Hugo on GitHub Pages
walked through getting a Hugo site onto GitHub Pages, a GitHub Actions
workflow that builds the site and lets GitHub serve the result for free.
Cloudflare Pages does the same job, hosting a folder of
static files, but the path to get there is different. Instead of a
Git-connected build, you push an already-built directory straight up with
Wrangler
, Cloudflare’s
CLI, from your own CI. Same Hugo site, same public/ output, a different
way of shipping it.
Prerequisites
- Hugo Extended
,
0.146.0or newer. - Node.js and npm, to run Wrangler.
- A Cloudflare account.
- A Hugo site. If you don’t have one yet, check the previous post for that from an empty folder. A GitHub repo for it only matters later, for the GitHub Actions section.
Create a Cloudflare Pages project
Cloudflare Pages has two ways to get a site onto it: connect a Git repo and let Cloudflare run the build, or direct upload a folder you already built yourself. This post uses direct upload, since the build already happens in GitHub Actions and there’s no reason to run it a second time on Cloudflare’s side. A direct upload project can’t be switched to Git integration later, so decide up front if you might want Cloudflare running the build too.
Log in and create the project with Wrangler:
npx wrangler login
npx wrangler pages project createIt asks for a project name and a production branch, then the project exists
and is served at <PROJECT_NAME>.pages.dev until you attach a custom
domain.
Deploy the build by hand
Build the site the same way as before:
hugo --gc --minifyThen push the public/ directory up with Wrangler:
npx wrangler pages deploy public --project-name=<PROJECT_NAME>That’s a full deploy on its own, useful the first time to make sure the project and the build output line up before wiring up CI.
Automate the deploy with GitHub Actions
Deploying from a laptop works, but the point of the GitHub Pages post was
never touching the deploy step by hand again. Wrangler needs two values to
run from CI: an API token and the account ID, both from the Cloudflare
dashboard’s API Tokens page and Account Home overview respectively.
Add them as repository secrets, CLOUDFLARE_API_TOKEN and
CLOUDFLARE_ACCOUNT_ID, under Settings > Secrets and variables > Actions.
The workflow reuses the Hugo install step from the GitHub Pages post, then
hands the build off to cloudflare/wrangler-action
instead of actions/deploy-pages:
# .github/workflows/deploy-cloudflare.yaml
name: Deploy Hugo site to Cloudflare Pages
on:
push:
branches:
- master
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
env:
HUGO_VERSION: 0.165.0
steps:
- name: Install Hugo CLI
run: |
wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb
sudo dpkg -i ${{ runner.temp }}/hugo.deb
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Build with Hugo
env:
HUGO_ENVIRONMENT: production
HUGO_ENV: production
run: |
hugo --gc --minify
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy public --project-name=<PROJECT_NAME>
gitHubToken: ${{ secrets.GITHUB_TOKEN }}One difference from the GitHub Pages workflow: there’s no --baseURL
override on the build step. actions/configure-pages needed that because
GitHub can serve a project from a subpath. Cloudflare Pages always serves
from the domain root, so whatever baseURL is already set in hugo.toml
is correct as is.
Push, and the Actions tab shows the same build-then-deploy shape as the
GitHub Pages workflow, just handing the finished files to Wrangler instead
of actions/deploy-pages.
Conclusion
Direct upload is the whole difference here: GitHub Pages ties the
build to its own Pages integration, Cloudflare Pages with direct upload
doesn’t care where public/ came from, only that it’s a folder of files
and a Wrangler command pointed at it. Same Hugo site, same GitHub Actions
trigger, just a different platform on the receiving end.
Between this post and the previous one , that’s a Hugo site deployed for free both ways, GitHub Pages and Cloudflare Pages. Pick whichever one the rest of your stack already leans on.