{
  "id": "2025/07/deploying-a-hugo-blog-to-cloudflare-workers9",
  "data": {
    "title": "Deploying a Hugo Blog to Cloudflare Workers",
    "description": "How I setup hugo to deploy automatically to cloudflare workers",
    "pubDate": "2025-07-25T09:21:47.000Z",
    "image": {
      "src": "/_astro/cover.CGjrfB_A.jpg",
      "width": 1280,
      "height": 720,
      "format": "jpg"
    },
    "links": [
      {
        "title": "Joe Mooring's initial post",
        "description": "Hugo community support page.",
        "url": "https://discourse.gohugo.io/t/hugo-support-in-cloudflare-workers/54866/4"
      },
      {
        "title": "GitHub Repo",
        "description": "Joe's git repo with an example of a hugo site configured to deploy to cloudflare workers.",
        "url": "https://github.com/jmooring/hosting-cloudflare-worker"
      }
    ],
    "tags": [
      "hugo",
      "cloudflare",
      "build",
      "deploy"
    ],
    "categories": [
      "guide"
    ]
  },
  "body": "> A large part of this guide builds on the work done\n> by [Joe Mooring](https://discourse.gohugo.io/t/hugo-support-in-cloudflare-workers/54866/4).\n\nI've been umming and ahing about what I should use to build this blog. Do I use Astro or maybe SvelteKit or what about\nTanstack Start? After multiple failed starts, I just decided to go back to basics, so I chose hugo and we will see where\nthings go from here. (Also it's a change from day to day as all the node.js frameworks have become very samey).\n\nI adapted the build script to use UTC, and I removed the SCSS build as I do not need it. While the CloudFlare build\nservice does have hugo available the version is fixed. Using the build script to download the specific version of hugo,\nthis prevents any potential build issues due to version mismatches.\n\nThe script downloads a spcific hugo version, does a bit of clean up, adds the hugo binary to the execution path, and has\na fix for an issue where hugo cannot read git info if there is only one commit.\n\n```shell\nmain() {\n  HUGO_VERSION=0.148.1\n\n  export TZ=UTC\n\n  # Install Hugo\n  echo \"Installing Hugo v${HUGO_VERSION}...\"\n  curl -LJO https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz\n  tar -xf \"hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz\"\n  cp hugo /opt/buildhome\n  rm LICENSE README.md hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz\n\n  # Set PATH\n  echo \"Setting the PATH environment variable...\"\n  export PATH=/opt/buildhome:$PATH\n\n  # Verify installed versions\n  echo \"Verifying installations...\"\n  echo Go: \"$(go version)\"\n  echo Hugo: \"$(hugo version)\"\n  echo Node.js: \"$(node --version)\"\n\n  # https://github.com/gohugoio/hugo/issues/9810\n  git config core.quotepath false\n\n  # Build the site.\n  hugo --gc --minify\n\n}\n\nset -euo pipefail\nmain \"$@\"\n```\n\nThe wrangler cli is used to manage deployments to CloudFlare workers. You can configure an asset only deploy using the\n`wrangler.jsonc` file.\n\nThis file tells wrangler to take the public directory created by hugo and deploy it to CloudFlares CDN, along with the\nbuild command to use.\n\nThe routes option allows you to add your domain to the worker.\n\n```json\n{\n    \"$schema\": \"node_modules/wrangler/config-schema.json\",\n    \"name\": \"website\",\n    \"compatibility_date\": \"2025-05-21\",\n    \"assets\": {\n        \"directory\": \"./public\",\n        \"html_handling\": \"auto-trailing-slash\",\n        \"not_found_handling\": \"404-page\",\n        \"run_worker_first\": false\n    },\n    \"build\": {\n        \"command\": \"./build.sh\"\n    },\n    \"routes\": [\n        {\n            \"pattern\": \"your-domain.net\",\n            \"custom_domain\": true\n        },\n        {\n            \"pattern\": \"www.your-domain.net\",\n            \"custom_domain\": true\n        }\n    ]\n}\n```\n\nIn CloudFlare you would need the\nfollowing [build configuration](https://developers.cloudflare.com/workers/ci-cd/builds/configuration/):\n\nThis will deploy the `main` branch to your primary domain on every commit and will use the non-production command to\ndeploy a branch to a dynamic workers.dev domain which you can use to preview posts before making them public.\n\n```text\nBuild command: ./build.sh\n\nDeploy command: pnpx wrangler deploy\n\nNon-production branch deploy command: pnpx wrangler versions upload\n\nPath: /\n```\n\nWhen you are using previews you can access the branch on a custom domain\nlike https://branch-name.worker.your-subdomain.workers.dev/. You can write different posts on each branch which is great\nif you have multiple posts at different stages of readiness.\n\n> Image\n> by [Pexels](https://pixabay.com/users/pexels-2286921/)\n> from [Pixabay](https://pixabay.com/)",
  "filePath": "src/content/blog/2025/07/deploying-a-hugo-blog-to-cloudflare-workers9.md",
  "assetImports": [
    "../../../../assets/cover.jpg"
  ],
  "digest": "79e42b0f1a5b3c2d",
  "rendered": {
    "html": "<blockquote>\n<p>A large part of this guide builds on the work done\nby <a href=\"https://discourse.gohugo.io/t/hugo-support-in-cloudflare-workers/54866/4\">Joe Mooring</a>.</p>\n</blockquote>\n<p>I’ve been umming and ahing about what I should use to build this blog. Do I use Astro or maybe SvelteKit or what about\nTanstack Start? After multiple failed starts, I just decided to go back to basics, so I chose hugo and we will see where\nthings go from here. (Also it’s a change from day to day as all the node.js frameworks have become very samey).</p>\n<p>I adapted the build script to use UTC, and I removed the SCSS build as I do not need it. While the CloudFlare build\nservice does have hugo available the version is fixed. Using the build script to download the specific version of hugo,\nthis prevents any potential build issues due to version mismatches.</p>\n<p>The script downloads a spcific hugo version, does a bit of clean up, adds the hugo binary to the execution path, and has\na fix for an issue where hugo cannot read git info if there is only one commit.</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"shell\"><code><span class=\"line\"><span style=\"color:#B392F0\">main</span><span style=\"color:#E1E4E8\">() {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">  HUGO_VERSION</span><span style=\"color:#F97583\">=</span><span style=\"color:#9ECBFF\">0.148.1</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">  export</span><span style=\"color:#E1E4E8\"> TZ</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">UTC</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\">  # Install Hugo</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">  echo</span><span style=\"color:#9ECBFF\"> \"Installing Hugo v${</span><span style=\"color:#E1E4E8\">HUGO_VERSION</span><span style=\"color:#9ECBFF\">}...\"</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">  curl</span><span style=\"color:#79B8FF\"> -LJO</span><span style=\"color:#9ECBFF\"> https://github.com/gohugoio/hugo/releases/download/v</span><span style=\"color:#E1E4E8\">${HUGO_VERSION}</span><span style=\"color:#9ECBFF\">/hugo_extended_</span><span style=\"color:#E1E4E8\">${HUGO_VERSION}</span><span style=\"color:#9ECBFF\">_linux-amd64.tar.gz</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">  tar</span><span style=\"color:#79B8FF\"> -xf</span><span style=\"color:#9ECBFF\"> \"hugo_extended_${</span><span style=\"color:#E1E4E8\">HUGO_VERSION</span><span style=\"color:#9ECBFF\">}_linux-amd64.tar.gz\"</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">  cp</span><span style=\"color:#9ECBFF\"> hugo</span><span style=\"color:#9ECBFF\"> /opt/buildhome</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">  rm</span><span style=\"color:#9ECBFF\"> LICENSE</span><span style=\"color:#9ECBFF\"> README.md</span><span style=\"color:#9ECBFF\"> hugo_extended_</span><span style=\"color:#E1E4E8\">${HUGO_VERSION}</span><span style=\"color:#9ECBFF\">_linux-amd64.tar.gz</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\">  # Set PATH</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">  echo</span><span style=\"color:#9ECBFF\"> \"Setting the PATH environment variable...\"</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">  export</span><span style=\"color:#E1E4E8\"> PATH</span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\">/opt/buildhome:$PATH</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\">  # Verify installed versions</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">  echo</span><span style=\"color:#9ECBFF\"> \"Verifying installations...\"</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">  echo</span><span style=\"color:#9ECBFF\"> Go:</span><span style=\"color:#9ECBFF\"> \"$(</span><span style=\"color:#B392F0\">go</span><span style=\"color:#9ECBFF\"> version)\"</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">  echo</span><span style=\"color:#9ECBFF\"> Hugo:</span><span style=\"color:#9ECBFF\"> \"$(</span><span style=\"color:#B392F0\">hugo</span><span style=\"color:#9ECBFF\"> version)\"</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">  echo</span><span style=\"color:#9ECBFF\"> Node.js:</span><span style=\"color:#9ECBFF\"> \"$(</span><span style=\"color:#B392F0\">node</span><span style=\"color:#79B8FF\"> --version</span><span style=\"color:#9ECBFF\">)\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\">  # https://github.com/gohugoio/hugo/issues/9810</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">  git</span><span style=\"color:#9ECBFF\"> config</span><span style=\"color:#9ECBFF\"> core.quotepath</span><span style=\"color:#79B8FF\"> false</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6A737D\">  # Build the site.</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">  hugo</span><span style=\"color:#79B8FF\"> --gc</span><span style=\"color:#79B8FF\"> --minify</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">set</span><span style=\"color:#79B8FF\"> -euo</span><span style=\"color:#9ECBFF\"> pipefail</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">main</span><span style=\"color:#9ECBFF\"> \"</span><span style=\"color:#79B8FF\">$@</span><span style=\"color:#9ECBFF\">\"</span></span></code></pre>\n<p>The wrangler cli is used to manage deployments to CloudFlare workers. You can configure an asset only deploy using the\n<code>wrangler.jsonc</code> file.</p>\n<p>This file tells wrangler to take the public directory created by hugo and deploy it to CloudFlares CDN, along with the\nbuild command to use.</p>\n<p>The routes option allows you to add your domain to the worker.</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"json\"><code><span class=\"line\"><span style=\"color:#E1E4E8\">{</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">    \"$schema\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"node_modules/wrangler/config-schema.json\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">    \"name\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"website\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">    \"compatibility_date\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"2025-05-21\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">    \"assets\"</span><span style=\"color:#E1E4E8\">: {</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">        \"directory\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"./public\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">        \"html_handling\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"auto-trailing-slash\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">        \"not_found_handling\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"404-page\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">        \"run_worker_first\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#79B8FF\">false</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    },</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">    \"build\"</span><span style=\"color:#E1E4E8\">: {</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">        \"command\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"./build.sh\"</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    },</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">    \"routes\"</span><span style=\"color:#E1E4E8\">: [</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">        {</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">            \"pattern\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"your-domain.net\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">            \"custom_domain\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#79B8FF\">true</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">        },</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">        {</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">            \"pattern\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#9ECBFF\">\"www.your-domain.net\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#79B8FF\">            \"custom_domain\"</span><span style=\"color:#E1E4E8\">: </span><span style=\"color:#79B8FF\">true</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">        }</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    ]</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}</span></span></code></pre>\n<p>In CloudFlare you would need the\nfollowing <a href=\"https://developers.cloudflare.com/workers/ci-cd/builds/configuration/\">build configuration</a>:</p>\n<p>This will deploy the <code>main</code> branch to your primary domain on every commit and will use the non-production command to\ndeploy a branch to a dynamic workers.dev domain which you can use to preview posts before making them public.</p>\n<pre class=\"astro-code github-dark\" style=\"background-color:#24292e;color:#e1e4e8; overflow-x: auto;\" tabindex=\"0\" data-language=\"text\"><code><span class=\"line\"><span>Build command: ./build.sh</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>Deploy command: pnpx wrangler deploy</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>Non-production branch deploy command: pnpx wrangler versions upload</span></span>\n<span class=\"line\"><span></span></span>\n<span class=\"line\"><span>Path: /</span></span></code></pre>\n<p>When you are using previews you can access the branch on a custom domain\nlike <a href=\"https://branch-name.worker.your-subdomain.workers.dev/\">https://branch-name.worker.your-subdomain.workers.dev/</a>. You can write different posts on each branch which is great\nif you have multiple posts at different stages of readiness.</p>\n<blockquote>\n<p>Image\nby <a href=\"https://pixabay.com/users/pexels-2286921/\">Pexels</a>\nfrom <a href=\"https://pixabay.com/\">Pixabay</a></p>\n</blockquote>",
    "metadata": {
      "headings": [],
      "localImagePaths": [],
      "remoteImagePaths": [],
      "frontmatter": {
        "title": "Deploying a Hugo Blog to Cloudflare Workers",
        "description": "How I setup hugo to deploy automatically to cloudflare workers",
        "pubDate": "2025-07-25T11:21:47+02:00",
        "image": "../../../../assets/cover.jpg",
        "links": [
          {
            "title": "Joe Mooring's initial post",
            "description": "Hugo community support page.",
            "url": "https://discourse.gohugo.io/t/hugo-support-in-cloudflare-workers/54866/4"
          },
          {
            "title": "GitHub Repo",
            "description": "Joe's git repo with an example of a hugo site configured to deploy to cloudflare workers.",
            "url": "https://github.com/jmooring/hosting-cloudflare-worker"
          }
        ],
        "tags": [
          "hugo",
          "cloudflare",
          "build",
          "deploy"
        ],
        "categories": [
          "guide"
        ],
        "minutesRead": "3 minute read"
      },
      "imagePaths": []
    }
  },
  "collection": "blog"
}
Richard Banks

A large part of this guide builds on the work done by Joe Mooring.

I’ve been umming and ahing about what I should use to build this blog. Do I use Astro or maybe SvelteKit or what about Tanstack Start? After multiple failed starts, I just decided to go back to basics, so I chose hugo and we will see where things go from here. (Also it’s a change from day to day as all the node.js frameworks have become very samey).

I adapted the build script to use UTC, and I removed the SCSS build as I do not need it. While the CloudFlare build service does have hugo available the version is fixed. Using the build script to download the specific version of hugo, this prevents any potential build issues due to version mismatches.

The script downloads a spcific hugo version, does a bit of clean up, adds the hugo binary to the execution path, and has a fix for an issue where hugo cannot read git info if there is only one commit.

main() {
  HUGO_VERSION=0.148.1

  export TZ=UTC

  # Install Hugo
  echo "Installing Hugo v${HUGO_VERSION}..."
  curl -LJO https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz
  tar -xf "hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz"
  cp hugo /opt/buildhome
  rm LICENSE README.md hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz

  # Set PATH
  echo "Setting the PATH environment variable..."
  export PATH=/opt/buildhome:$PATH

  # Verify installed versions
  echo "Verifying installations..."
  echo Go: "$(go version)"
  echo Hugo: "$(hugo version)"
  echo Node.js: "$(node --version)"

  # https://github.com/gohugoio/hugo/issues/9810
  git config core.quotepath false

  # Build the site.
  hugo --gc --minify

}

set -euo pipefail
main "$@"

The wrangler cli is used to manage deployments to CloudFlare workers. You can configure an asset only deploy using the wrangler.jsonc file.

This file tells wrangler to take the public directory created by hugo and deploy it to CloudFlares CDN, along with the build command to use.

The routes option allows you to add your domain to the worker.

{
    "$schema": "node_modules/wrangler/config-schema.json",
    "name": "website",
    "compatibility_date": "2025-05-21",
    "assets": {
        "directory": "./public",
        "html_handling": "auto-trailing-slash",
        "not_found_handling": "404-page",
        "run_worker_first": false
    },
    "build": {
        "command": "./build.sh"
    },
    "routes": [
        {
            "pattern": "your-domain.net",
            "custom_domain": true
        },
        {
            "pattern": "www.your-domain.net",
            "custom_domain": true
        }
    ]
}

In CloudFlare you would need the following build configuration:

This will deploy the main branch to your primary domain on every commit and will use the non-production command to deploy a branch to a dynamic workers.dev domain which you can use to preview posts before making them public.

Build command: ./build.sh

Deploy command: pnpx wrangler deploy

Non-production branch deploy command: pnpx wrangler versions upload

Path: /

When you are using previews you can access the branch on a custom domain like https://branch-name.worker.your-subdomain.workers.dev/. You can write different posts on each branch which is great if you have multiple posts at different stages of readiness.

Image by Pexels from Pixabay