Chris Wiegman

Deploying My Hugo Site to Cloudflare Workers

| 5 min read
Deploying My Hugo Site to Cloudflare Workers

This is the first of a few posts I have planned on my new Hugo workflow. I’m starting here with the basics of getting the site itself running. In future posts I’ll cover a usable workflow to draft and publish content from any device, just like a fancy content management system would do.

When I last had this site on Cloudflare I used Cloudflare Pages. That worked well at the time but Cloudflare Pages isn’t getting the attention it used to. Instead Cloudflare is focusing on its Workers product which is generally more robust, but does pose a few challenges for deploying a static site such as this one which is made with Hugo.

It took a little trial and error, but now I’m pretty happy with the this site on Workers. The catch is the default configuration recommended by Cloudflare, where Workers connects directly to my repository just didn’t work for me.

What I needed was a few things:

  1. The ability to reliably deploy when new content is pushed (this is where Workers’ recommended connection to GitHub was fine)
  2. The ability to easily deploy on demand for any reason I needed to
  3. The ability to deploy the site on a schedule to handle scheduled content such as this post
  4. Faster deploys than their default mechanism

Thankfully, this wasn’t too hard to get. My configuration involves an updated wrangler.toml configuration as well as a GitHub Action that does all the actual work. Here’s the relevant files:

The Wrangler Config

Wrangler serves as the controller, if you will, for Cloudflare Workers. It’s what starts the server and actually gets your code working. This is the big difference from Cloudflare Pages which just served any files it had automatically.

To run a Hugo site on Cloudflare workers add a file to the root of your site called wrangler.toml with the following configuration:

1
2
3
4
5
6
7
8
name = "chriswiegman-com"
compatibility_date = "2025-07-31"
preview_urls = true

[assets]
directory = "./public"
not_found_handling = "404-page"
html_handling = "auto-trailing-slash"

The only thing you’re going to need to change for certain is the name field on line 1 to reflect the name of your worker. The rest should work fine with most Hugo builds.

The GitHub Action

Next up, the GitHub Action. For this you’ll want to save the following code to .github/workflows/deploy.yml in your GitHub repo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
name: Deploy to Cloudflare Workers

env:
  SITE_TITLE: "Chris Wiegman"
  SITE_URL: "https://chriswiegman.com/"
  RSS_URL: "https://chriswiegman.com/index.xml"


on:
    push:
        branches: [main]
        paths-ignore:
            - "content/drafts/**"
            - ".vscode/**"
            - "scripts/**"
            - ".github/**"
            - "Makefile"
            - "package.json"
            - "package-lock.json"
            - "README.md"
            - ".gitignore"
            - "/gitmodules"
    workflow_dispatch: {}
    workflow_call:
        inputs:
            ref:
                required: true
                type: string
    schedule:
        - cron: "0 15 * * *"

permissions:
    contents: read

concurrency:
    group: ${{ github.workflow }}
    cancel-in-progress: true

jobs:
    deploy:
        runs-on: ubuntu-latest

        steps:
            - name: Checkout
              uses: actions/checkout@v6
              with:
                  ref: ${{ inputs.ref }}
                  submodules: true
                  fetch-depth: 2

            - name: Setup Hugo
              uses: peaceiris/actions-hugo@v3
              with:
                  hugo-version: "latest"
                  extended: true

            - name: Cache Hugo resources
              uses: actions/cache@v5
              with:
                  path: |
                      resources
                  key: ${{ runner.os }}-hugo-resources-${{ hashFiles('**/go.mod', '**/go.sum', '**/package-lock.json', '**/pnpm-lock.yaml', '**/yarn.lock', 'hugo.toml', 'hugo.yaml', 'hugo.json', 'config/**', 'assets/**') }}
                  restore-keys: |
                      ${{ runner.os }}-hugo-resources-

            - name: Cache Hugo cacheDir
              uses: actions/cache@v5
              with:
                  path: |
                      .hugo_cache
                  key: ${{ runner.os }}-hugo-cachedir-${{ hashFiles('**/go.mod', '**/go.sum', 'hugo.toml', 'hugo.yaml', 'hugo.json', 'config/**') }}
                  restore-keys: |
                      ${{ runner.os }}-hugo-cachedir-

            - name: Build site
              run: hugo --gc --minify --cacheDir "${{ github.workspace }}/.hugo_cache"

            - name: Set up Node (with npm cache)
              uses: actions/setup-node@v6
              with:
                  node-version: "lts/*"
                  cache: "npm"

            - name: Install Wrangler (cached)
              run: npm ci

            - name: Deploy to Cloudflare Workers
              run: npx wrangler deploy
              env:
                  CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
                  CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

Of course you’ll want to change your site info on the first 3 lines. You’ll also need to set two secrets in GitHub: CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.

The latter is available on the right-hand column of the Overview page for your Cloudflare domain.

The API key can be created under your Cloudflare profile by going to API Tokens. You’ll need to add the following permissions:

  • <your user> Scripts:Edit
  • All users - User Details:Read

The Action itself does two things:

  1. It runs daily at 09:00 CST as well as running on push and running “on demand” in that you can manually trigger it from the “Actions” page of your GitHub repository.
  2. It builds your site, but it also caches the build which can make it much faster. On my little site it knocks the build time from ~5 minutes to around 40 seconds, mostly due to all the image processing I do.
  3. It installs Cloudflare’s Wrangler and deploys it per our Wrangler script above.

To use Wrangler in this fashion, you’ll need to install it in your Hugo site;s repo with npm. Use the following in your repo’s directory to install it:

npm i -D wrangler@latest

Note that Wrangler is only needed for deployment. Wrangler is saved as dev script so it shouldn’t affect anything else on your site.

That’s it. With those two files deployed to your site and Wrangler in your package.json file your Hugo site should be fast and available on Cloudflare Workers.