Omegion

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

  1. Hugo Extended , 0.146.0 or newer.
  2. Node.js and npm, to run Wrangler.
  3. A Cloudflare account.
  4. 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:

shell
npx wrangler login
npx wrangler pages project create

It 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:

shell
hugo --gc --minify

Then push the public/ directory up with Wrangler:

shell
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:

yaml
# .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.