Skip to content
0015 Docs toolchain: Hugo with Hextra, a Diátaxis docs/ folder and generated reference

0015 Docs toolchain: Hugo with Hextra, a Diátaxis docs/ folder and generated reference

Status: Accepted. The owner said yes at Gate 1 on 15 October 2026.

The evidence is in the research note on Go project structure, sections 2.7 and 3.

Context

The goal asks for:

  • user docs split by Diátaxis (tutorials, how-to guides, reference, explanation), apart from the contributor docs;
  • reference docs generated from the code: the CLI and man pages, the config, the keymap, the agent table and the shell completions;
  • a site with search, dark and light themes, and versioned docs per release;
  • a static build that CI attaches as an artifact, and GitHub Pages once the repo is public;
  • link, Markdown, spelling and plain-English checks in CI.

Contributors need Go, just and golangci-lint today, so the toolchain should fit a Go repo and add as few runtimes as it can.

Two facts from October 2026 shape the choice. Material for MkDocs has been in maintenance mode since 5 November 2025, and MkDocs has had no stable release since August 2024. And GoReleaser, the release tool we already use, moved its own site from Material for MkDocs to Hugo with the Hextra theme on 22 March 2026.

Options

  1. Hugo with the Hextra theme. One Go binary. FlexSearch and a light, dark and system toggle are built in. We build the versions ourselves.
  2. Zensical. The successor to Material for MkDocs, on Python 3.11 or later. It is at 0.0.70, and versioning works only through a transitional fork of mike.
  3. MkDocs with Material and mike. Mature, but in maintenance mode.
  4. Docusaurus. Built-in versioning, but it needs Node and React, and local search needs a plugin.
  5. Starlight. Pagefind search is built in. It needs Node, and versioning is an early-stage plugin.
  6. VitePress or mdBook. Neither has built-in versioning.
  7. No site. Markdown on GitHub only, with docs/ and docs-master/ as lazygit does.

Decision

Option 1, set up like this:

  • Content stays as plain Markdown in docs/, readable on GitHub, in tutorials/, how-to/, reference/ and explanation/. Contributor docs go in docs/contributing/. Each page has a title in its front matter.

  • The site lives in site/: hugo.yaml, and a nested go.mod that pins Hextra, so it stays out of the main module. It mounts ../docs as content. The just recipe sets the site title from internal/brand, so the config never spells the name. Relative image links need uglyURLs: true or an image render hook. The spike showed that Hugo’s default URLs break them.

  • Reference is generated by cmd/docgen (0014) through just docs, and is never edited by hand:

    • Markdown pages for each command, from cobra’s doc package;
    • the config, keymap and agent pages, from the code.

    Man pages and completions are made into a build folder and in GoReleaser’s before.hooks; they are not committed. CI runs just docs and fails on any diff.

  • Versions. At each release, CI builds that version’s site and attaches it to the release as a tarball. Once the repo is public, a Pages job puts the newest release at /, each release at /vX.Y/, and main at /dev/.

  • CI builds the site on every push and uploads it as an artifact. It runs lychee for links, markdownlint-cli2 for Markdown, and typos with locale = "en-gb" for spelling. Vale checks plain English, as warnings at first.

  • Versions are pinned: Hugo in CI and in the setup recipe, and Hextra in site/go.mod, which Dependabot keeps up to date.

The reasons:

  • Hugo adds one binary and no Node or Python. Most contributors never need it, because CI builds the site.
  • Search and both themes come built in.
  • Versions are immutable once built.
  • It is the same choice as GoReleaser, on the same stack.

A spike with Hugo 0.168.0 and Hextra 0.13.0 confirmed the search index, the theme toggle, relative .md links from a mounted docs/ folder, and a title set from the environment.

Consequences

  • Good, because the docs stay Markdown in the repo, and the generator can be swapped by changing site/ only.
  • Good, because generated reference and the stale-docs check make it hard for the docs to drift from the code.
  • Good, because old versions are never rebuilt with newer tools, so they cannot break.
  • Bad, because versioning is our own script, not a feature of the tool, and the version menu must be kept up to date.
  • Bad, because Hextra is at 0.x, and one maintainer writes most of it. We pin it and keep the content portable.
  • Bad, because previewing the site needs Hugo’s extended edition, and the Markdown lint needs Node in CI.
  • Bad, because every page needs title front matter, which GitHub shows as a small table at the top of the file.
  • Revisit when Zensical reaches 1.0 with native versioning.

View the source of this page Suggest a change