Step-by-step instructions to build and maintain a Hugo + PaperMod + AsciiDoc static site on GitHub Pages.

Prerequisites

  • Homebrew (macOS) or equivalent package manager

  • Git

  • Hugo Extended (installed via Homebrew)

brew install hugo
hugo version

Verify the output includes extended:

hugo v0.164.0+extended ...

Asciidoctor

Hugo requires asciidoctor to process .adoc files:

brew install asciidoctor
gem install asciidoctor

Verify:

asciidoctor --version

Project Setup

Create a New Site

hugo new site my-site
cd my-site

Add the PaperMod Theme

git init
git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod themes/PaperMod

Configure hugo.toml

baseURL = "https://example.com/"
locale = "en"
title = "My Site"
theme = "PaperMod"

paginate = 10
enableRobotsTXT = true
enableEmoji = true

[params]
  description = "Site description"
  author = "Your Name"
  defaultTheme = "auto"
  ShowReadingTime = true
  ShowToc = true
  ShowCodeCopyButtons = true

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

[menu]
  main = [
    { identifier = "posts", name = "Posts", url = "/posts/", weight = 10 },
    { identifier = "about", name = "About", url = "/about/", weight = 20 },
  ]

[security.exec]
  allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^git$', '^node$', '^postcss$', '^tailwindcss$', '^asciidoctor$']

[markup.asciidocExt]
  workingFolderCurrent = true
  noHeaderOrFooter = true
  safeMode = "unsafe"
  attributes = { icons = "font", idseparator = "_", source-highlighter = "rouge", "rouge-style" = "monokai", experimental = true, sectanchors = true }

Create Content Directory Structure

mkdir -p content/posts
mkdir -p static/images
mkdir -p content/arch

Create an About Page

cat > content/about.md << 'EOF'
---
title: "About"
date: 2026-01-01
---
Your about page content here.
EOF

Create a First Post

---
title: "My First Post"
date: 2026-07-07
showToc: true
---

= My First Post

Content in AsciiDoc format.

== Section Heading

* Bullet point
* Another point

[source,sh]

echo "Code block"

Images

Place images in static/images/post-name/ and reference them in AsciiDoc:

my image

Adding Images

Images in Hugo are static files placed in the static/ directory. Only files inside static/ are copied to public/ during build.

For a Post with Images

mkdir -p static/images/my-post
cp /path/to/images/*.png static/images/my-post/

Then in your AsciiDoc:

scene 01

Hard-Linking Source Images

For content that is edited outside the project directory, use hard links so changes propagate automatically:

ln /path/to/source/image.png static/images/my-post/image.png
Note
Hard links share the same inode. Editing the source file updates the site copy. Hard links cannot cross filesystem boundaries.

Setting Up GitHub Actions

Create .github/workflows/hugo-deploy.yml:

name: "Deploy Hugo site to Pages"
on:
  push:
    branches:
      - main
  workflow_dispatch:

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

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

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: true
          fetch-depth: 0
      - uses: peaceiris/actions-hugo@v3
        with:
          hugo-version: "latest"
          extended: true
      - name: Install asciidoctor
        run: sudo apt-get update -qq && sudo apt-get install -y -qq asciidoctor
      - run: hugo --minify
      - uses: actions/upload-pages-artifact@v3
        with:
          path: ./public
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/deploy-pages@v4
Note
The submodules: true checkout option is required for the PaperMod theme. The asciidoctor install step is required because the GitHub Actions Ubuntu runner does not include asciidoctor by default.

GitHub Pages Settings — Critical

The Pages source setting must match the deployment method:

  1. Go to Settings > Pages

  2. Under "Source", select "GitHub Actions" (not "Deploy from a branch")

If "Deploy from a branch" is selected instead, the workflow will succeed but the site will not update. The old site continues serving from whatever branch was set there. This is the most common deploy failure mode.

Tip
After switching from "Deploy from a branch" to "GitHub Actions", push an empty commit or re-run the workflow manually to trigger a fresh deploy:
git commit --allow-empty -m "trigger deploy after Pages config change"
git push origin main

Custom Domain

When using the GitHub Actions workflow, the custom domain is managed in Settings > Pages under "Custom domain". Enter your domain there (e.g. www.rocasta.com).

A CNAME file in the repo root is optional with the workflow-based deploy — GitHub stores the domain setting in the Pages configuration, not in the repo. However, keeping a CNAME file is harmless and documents the domain.

Local Development

Build the Site

hugo

Output goes to public/.

Development Server with Live Reload

hugo server -D

This starts a server at http://localhost:1313/. The -D flag includes draft content. Hugo watches for file changes and automatically refreshes the browser.

Building with Drafts

hugo -D
hugo server -D

Gotchas and Troubleshooting

Hugo Blocks Asciidoctor

When using AsciiDoc, Hugo’s security policy blocks external executables by default. Add asciidoctor to [security.exec] allow in hugo.toml:

ERROR asciidoctor is not whitelisted in policy security.exec.allow

Fix: Add ^asciidoctor$ to the allow list.

Asciidoctor Not Found

Hugo: asciidoctor ... unable to locate

Install asciidoctor:

brew install asciidoctor

Theme Not Found

ERROR module "PaperMod" not found

Make sure the theme submodule is initialized:

git submodule update --init --recursive

Image 404 in Development

If images don’t load in the Hugo dev server, check: 1. Images are in static/ directory (not assets/) 2. The :imagesdir: in AsciiDoc matches the directory path 3. Image filenames are case-sensitive

Hugo serves static/ content at the root. An image at static/images/foo.png is served at /images/foo.png.

Git Submodule Updates

To update the PaperMod theme:

git submodule update --remote themes/PaperMod

Bundler 4.x Gemfile Resolution (Jekyll migration only)

Bundler 4.x walks parent directories to find a Gemfile. If running bundle exec in a subdirectory and a parent has a different Gemfile, bundler may resolve the wrong one. Workaround: Set BUNDLE_GEMFILE explicitly:

BUNDLE_GEMFILE=/path/to/project/Gemfile bundle exec jekyll build

Deploy Succeeds but Old Site Still Shows

If the GitHub Actions workflow succeeds but the live site hasn’t changed, check:

  1. Pages source setting — Settings > Pages > Source must be set to "GitHub Actions", not "Deploy from a branch". This is the #1 cause. When switching back to the Jekyll-Chirpy branch, the old "Deploy from a branch" setting may be silently reselected.

  2. Custom domain cleared — Switching the Pages source can reset the custom domain field. Verify www.rocasta.com is still entered under Settings > Pages > Custom domain.

  3. Diagnose with curl — Bypass browser cache and DNS:

    curl -sI https://www.rocasta.com/ | grep -i 'x-cache\|age:'

If this shows a Hugo-generated page but the browser shows the old Jekyll site, the issue is browser cache (see below).

  1. Check raw Pages URL — Bypass custom domain entirely to distinguish DNS from deploy failure:

    https://<username>.github.io/

If the raw URL shows the new site but the custom domain doesn’t, it’s a DNS or CDN cache issue.

Browser Cache After Deploy

GitHub Pages serves with Cache-Control: max-age=600 (10 minutes). After a deploy, browsers and CDN edge nodes may hold stale content.

If curl shows the new site but the browser doesn’t:

  • Safari: Develop menu (enable in Settings > Advanced) → Empty Caches (Cmd+Option+E) → reload

  • Firefox: Cmd+Shift+R (hard reload), or Dev Tools → right-click reload → "Empty Cache and Hard Reload"

  • Any browser: Open a private/incognito window — this always fetches uncached content

CI Must Install Asciidoctor

The GitHub Actions Ubuntu runner does not include asciidoctor. Without it, the Hugo build will fail silently or produce no output for .adoc files.

Always add an explicit install step before the hugo command in the workflow:

sudo apt-get update -qq && sudo apt-get install -y -qq asciidoctor

Node.js Deprecation Warnings in CI

The deploy workflow may show warnings like:

Node 20 is being deprecated. This workflow is running with Node 24 by default.

These are non-blocking informational messages from GitHub Actions. The workflow continues to function correctly.

Hugo Deprecation Warnings in Theme

PaperMod 7.x uses deprecated Hugo APIs:

WARN deprecated: .Language.LanguageDirection was deprecated
WARN deprecated: .Language.LanguageCode was deprecated

These are benign and come from the theme, not user code. They will be fixed in future PaperMod releases.

Migration Notes

This site was migrated from Jekyll + Chirpy to Hugo + PaperMod in July 2026. The migration was driven by a Ruby version mismatch: Jekyll requires Ruby ~> 3.1, but macOS 13+ ships Ruby 4.0.5, and rbenv/rvm maintenance was not worth the overhead. Hugo is a single Go binary with no runtime dependencies.

Key migration lessons:

  • No incremental migration — There is no easy path to slowly transition a Jekyll site to Hugo. It’s a full rebuild: rewrite layouts, convert Markdown to AsciiDoc, restructure config, re-theme.

  • Back up the old branch — Before deleting or overwriting the Jekyll main branch, save it:

    git branch -m main jekyll-chirpy
    git push origin jekyll-chirpy
  • PaperMod via submodule — Pinning via submodule (not git clone) keeps the theme versioned with the site. Update with git submodule update --remote themes/PaperMod.

  • AsciiDoc over Markdown — Hugo supports both. AsciiDoc is more expressive for technical writing (admonitions, cross-references, attribute substitution) and avoids Markdown flavour lock-in.

  • GitHub Actions must install asciidoctor — The CI runner doesn’t have it pre-installed. See the gotcha above.

  • Pages source is the #1 deploy gotcha — Switching to a workflow-based deploy requires explicitly setting Settings > Pages > Source to "GitHub Actions".

Hard links cannot: - Link directories (use symlinks for directories) - Cross filesystem boundaries - Be tracked by Git (Git stores content, not links)

Hard links are useful for keeping source content in sync with the site build directory, but use symlinks or copies for directories.