Hugo for GitHub Pages

Environment: Mac M4 24GB · macOS Tahoe 26.3

It’s my birthday. Most people go out for dinner or, I don’t know, have cake. I’m migrating my blog from Pelican to Hugo. I’ve been meaning to do it for a while and apparently “a Saturday where I turn another year older” is the motivation I needed. Happy birthday to me — here’s a static site generator.

Hugo is written in Go, it’s absurdly fast, and it deploys cleanly to GitHub Pages via Actions. Here are my notes.

Install Hugo

On macOS, Homebrew is the path of least resistance:

brew install hugo
hugo version

You want the extended version (Homebrew gives you this by default). It includes SCSS/SASS support, which most themes need.

Create the Site

mkdir -p ~/code/heyjdp.github.io
cd ~/code/heyjdp.github.io
hugo new site . --force

The --force flag lets Hugo initialise in a non-empty directory (useful if you’ve already got a README or .gitignore in there).

This gives you the skeleton:

.
├── archetypes/
├── content/
├── data/
├── hugo.toml
├── layouts/
├── static/
└── themes/

Initialise Git

git init
git add -A
git commit -m "chore: hugo new site"

Add a Theme

I’m using BeautifulHugo [1]. It’s clean, readable, handles code blocks well, and doesn’t try to do too much. Add it as a git submodule so your builds are reproducible:

git submodule add https://github.com/halogenica/beautifulhugo.git themes/beautifulhugo

Pin it to a specific commit so you’re not surprised by upstream changes:

cd themes/beautifulhugo
git log -1 --format="%H"    # grab the latest SHA
cd ../..

Now tell Hugo to use it. Edit hugo.toml:

baseURL       = "https://heyjdp.github.io/"
languageCode  = "en-us"
title         = "Dr Jas Powell"
theme         = "beautifulhugo"

pygmentsCodeFences  = true
pygmentsUseClasses  = true

[Params]
  subtitle   = "Remote CTO · Hands-on Engineer · Team Builder"
  selfHosted = true
  useHLJS    = false

[taxonomies]
  category = "categories"
  tag      = "tags"

[[menu.main]]
  name   = "Blog"
  url    = "/"
  weight = 1

[[menu.main]]
  name   = "About"
  url    = "/page/about/"
  weight = 2

[[menu.main]]
  name   = "Tags"
  url    = "/tags/"
  weight = 3

The selfHosted = true flag is important — it tells BeautifulHugo to serve fonts and icons locally instead of pulling from Google Fonts and CDNs. No third-party requests, no GDPR headaches.

Create Your First Post

mkdir -p content/post/hello-world
cat <<'EOF' > content/post/hello-world/index.md
---
title: "Hello World"
date: 2023-10-07
draft: false
tags:
  - meta
description: "First post. Testing the plumbing."
---

First post. If you can read this, Hugo and GitHub Pages are working.
EOF

Hugo uses page bundles — each post gets its own directory under content/post/. This keeps images and other assets co-located with the post that uses them.

Build and Preview Locally

hugo serve

Browse to http://localhost:1313. Hugo’s live reload is genuinely fast — save a file and the browser updates before you can alt-tab to it.

To do a full production build (minified, no drafts):

hugo --minify

Output lands in public/. Don’t commit this directory — GitHub Actions will build it for you.

Add it to .gitignore:

echo "public/" >> .gitignore
echo ".hugo_build.lock" >> .gitignore

Deploy with GitHub Actions

This is the bit that makes it all worth it. No ghp-import, no separate branch, no manual pushing of built files. Just push to main and GitHub handles the rest.

Create the workflow file:

mkdir -p .github/workflows

And add .github/workflows/hugo.yml:

name: Deploy Hugo site to GitHub Pages

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: recursive
          fetch-depth: 0

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

      - name: Build
        run: hugo --minify

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v2
        with:
          path: ./public

  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v2

A few things to note:

  • submodules: recursive — essential, otherwise your theme won’t be checked out and the build will fail with a cryptic error about missing layouts.
  • fetch-depth: 0 — needed if you use enableGitInfo = true in your config (Hugo can show last-modified dates from git history).
  • extended: true — you want Hugo Extended for SCSS support.

GitHub Repo Settings

One manual step. Go to your repo on GitHub:

Settings → Pages → Build and deployment → Source → GitHub Actions

This tells GitHub to use the Actions workflow instead of the legacy branch-based deployment. If you skip this step, your workflow will run but nothing will actually deploy.

Push and Watch It Go

git add -A
git commit -m "feat: initial hugo site with github actions deployment"
git remote add origin git@github.com:heyjdp/heyjdp.github.io.git
git push -u origin main

Give it a minute or two. Check the Actions tab in your repo to watch the build. Once the green tick appears, your site is live at https://heyjdp.github.io.

What I Like About This Setup

  • Speed. Hugo builds this site in under 100ms. Pelican took seconds. For a small blog that’s academic, but it makes the local dev loop genuinely pleasant.
  • Single binary. No virtualenv, no pip dependencies, no Python version juggling. brew install hugo and you’re done.
  • Git submodule for the theme. Pin it to a commit. Reproducible. No surprises.
  • GitHub Actions. Push to main, site deploys. No ghp-import, no separate gh-pages branch to manage.

The whole thing took about an hour to set up from scratch, and most of that was fiddling with the hugo.toml config until I was happy with the menu layout.

References

  1. BeautifulHugo theme
  2. Hugo Quick Start
  3. Hugo — Hosting on GitHub