Skip to content

This is the multi-page printable view of this section. .

Return to the regular view of this page.

Get started

Start from the official OINK Starter, establish a working local baseline, then customize content, language, brand, integrations, and deployment in that order.

The recommended path for a new site starts from pgsty/oink-starter, not from a copy of this documentation and regression repository. The Starter is a public GitHub template: it pins OINK v1.0.0, builds as-is, and contains only neutral project content and deployment workflows.

Two version numbers have different jobs

OINK’s declared compatibility floor is Hugo Extended 0.160.1. The current Starter and its CI use Hugo Extended 0.165.0 and Go 1.27. Use that pinned Starter toolchain for the path below; use the lower floor only when maintaining an existing site that deliberately supports it.

Choose a path

Starting point Recommended path Result
New documentation or project site OINK Starter A small three-language Docs, Blog, and Book site with two deployment workflows
Existing Hugo site From scratch Add the OINK module and required Goldmark settings without replacing content
Existing Docsy or older OINK site Upgrade Preserve content, migrate supported syntax, and review site overrides

Five-minute baseline

  1. Install the tools

    Install Git, Go 1.27 or newer, and Hugo Extended 0.165.0 or newer. The Hugo output must contain extended:

    $ go version
    go version go1.27.0 darwin/arm64
    $ hugo version
    hugo v0.165.0+extended+withdeploy darwin/arm64
    

    On macOS, brew install git go hugo supplies them. On Linux and Windows, use the official Hugo installation guide and Go downloads; choose Hugo Extended.

  2. Create or clone the site

    For a repository you intend to keep, open the Starter and select Use this template, then clone the repository GitHub created for you. To evaluate the untouched original locally:

    git clone https://github.com/pgsty/oink-starter.git my-docs
    cd my-docs
    hugo server
  3. Open the baseline

    Open http://localhost:1313/. The default Starter also publishes Chinese at /zh/ and French at /fr/. Confirm that Docs, Blog, Book, search, language switching, and light/dark mode all work before editing anything.

  4. Make one visible change

    Change the title and canonical URL at the top of hugo.yaml, then edit one sentence in data/home/en.yaml. A browser reload that shows both changes is the first useful proof that configuration, content, and the pinned theme are connected correctly.

Customize from shallow to deep

  • Use OINK Starter — identity first, then languages, home page, content, navigation, brand, integrations, and deployment.
  • Starter repository tour — which file owns each part of the site, what to replace, and what can be removed.
  • Writing pages — front matter, headings, links, images, drafts, and the page-end controls.
  • Components — add expression only after the content tree is stable.
  • Brand and appearance — logo, accent, typography, width, and CSS extension points.
  • Deploy — use the supplied GitHub Pages or Cloudflare Pages workflow, then verify the real public routes.

This order is deliberate. A site that first proves its build and content tree is easier to debug than one that changes languages, navigation, CSS, analytics, and hosting at the same time.

Publication gate

Before the first push, run the same warning-strict production build the Starter workflows use:

hugo --cleanDestinationDir --gc --minify --environment production \
  --printPathWarnings --panicOnWarning

Success means the command ends with Total in …, prints no warning or error, and public/ contains the language roots and representative Docs, Blog, and Book routes. It does not yet prove deployment: a local build, a commit, a push, a green workflow, and correct public rendering are separate gates.

Next

Start with the complete Starter tutorial. If the template deliberately carries more structure than your project needs, use the repository tour to remove it safely. Use From scratch only when adding OINK to an existing site or when you explicitly want to assemble every file yourself.

1 - Use OINK Starter

Turn the official starter into your project site, one controlled layer at a time — identity, languages, home page, content, navigation, brand, integrations, and deployment.

pgsty/oink-starter is the supported starting point for a new OINK site. It is deliberately smaller than oink.pgsty.com: no theme documentation, analytics account, comment repository, browser regression suite, or PGSTY-specific brand is copied into your project.

The current template pins OINK v1.0.0, Go 1.27, and Hugo Extended 0.165.0. Its default three-language, English-only, and English–Chinese profiles have all been built warning-strictly against that release.

What the template contains

Surface Included baseline First decision
Languages English, Simplified Chinese, French Keep all three, or select a supplied single/bilingual profile
Content Docs, Blog, and a short Book tutorial Rewrite the examples; delete a whole surface only when you do not need it
Home One compact data/home/<lang>.yaml per language Replace the project promise and destinations
Brand Neutral logo and favicon Keep them until real project artwork exists
Integrations Repository, Giscus, analytics, share, and feedback examples are commented Enable only complete configurations you intend to operate
Deployment GitHub Pages and Cloudflare Pages Direct Upload workflows Choose one production path and verify its real URL

The Starter’s own Book at /book/ is a four-chapter tour from preview to deployment. This page is the maintainer-grade version: it explains the order of changes, the boundaries between them, and the checks after each layer.

Create your repository

GitHub template, recommended

Open the Starter repository, select Use this template → Create a new repository, then clone the repository created under your account or organization:

git clone https://github.com/OWNER/PROJECT-DOCS.git
cd PROJECT-DOCS
hugo server

This gives your site its own Git history and keeps the original Starter as an upstream reference rather than as a remote you might accidentally push to.

Clone the original to evaluate it

For a disposable local evaluation:

git clone https://github.com/pgsty/oink-starter.git
cd oink-starter
hugo server

Do not start a real project by deleting this clone’s .git directory. GitHub’s template operation already creates the clean project boundary and preserves an auditable first commit.

Preview before changing anything

Open these routes:

  • /, /zh/, /fr/ — the three home pages;
  • /docs/, /blog/, /book/ — the three content surfaces;
  • one translated page, then the language switcher;
  • search and the light/dark control at a narrow viewport.

Also record the resolved module:

hugo mod graph | grep github.com/pgsty/oink

It should resolve github.com/pgsty/oink@v1.0.0. This unchanged preview is the baseline against which every later edit is judged.

Customize in layers

Layer 1: identity

Change the two marked values at the top of hugo.yaml:

hugo.yaml
title: &siteTitle Project Name
baseURL: https://example.org/

The YAML anchor carries the title to all enabled languages. Then change the copyright holder and, after the new repository exists, uncomment its links:

hugo.yaml
params:
  copyright:
    authors: '[Project contributors](https://example.org/community/)'
    from_year: 2026
  github_repo: https://github.com/OWNER/PROJECT-DOCS
  github_branch: main

Run hugo server again and check the browser title, footer, edit/history links, and canonical URL. Do not change the logo yet unless the project has final artwork; text identity is easier to review first.

Layer 2: language profile

The root configuration enables English, Chinese, and French. Before making other configuration edits, choose one of the supplied profiles when that is not your intended language set:

cp examples/hugo.single.yaml hugo.yaml     # English only
cp examples/hugo.bilingual.yaml hugo.yaml  # English + Chinese

These are complete minimal configurations, not fragments: copying one replaces the commented integration examples in the root file. Do it at the beginning; if hugo.yaml already contains project changes, merge the languages and disableLanguages sections instead of overwriting it.

Disabled languages stay declared so Hugo recognizes .zh.md and .fr.md as translations and safely ignores them. If you remove a language permanently, remove its content and home data only after the selected profile builds.

Layer 3: home page

The home page is data rather than an opaque layout override:

data/home/en.yaml
data/home/zh.yaml
data/home/fr.yaml

Edit one language first. In each file, sections fixes the order; hero, cards, and cta provide the content. Replace the promise, destination URLs, and sample card copy while keeping the structure. After the first language is right, translate the same information into the enabled peers.

For another composition, use the full registry in Home and landing pages; do not copy the Starter home partial, because there is no site-specific template to copy.

Layer 4: content and navigation

Rewrite or remove sample leaf pages under content/. Keep section roots until you decide whether that whole surface belongs in your project:

content/docs/  reference and task documentation
content/blog/  posts, design records, and release announcements
content/book/  a sequential long-form guide

The content tree becomes the sidebar. Top navigation lives in menus.main on the translated _index roots, so renaming Docs, Blog, or Book happens beside the content it names rather than in a second global menu tree. Keep translated files side by side and give corresponding headings the same explicit IDs:

page.md
page.zh.md
page.fr.md

Follow Organizing content before creating a custom navigation data file; the generated tree is enough for most sites.

Layer 5: brand and reader features

Replace assets/icons/logo.svg and static/favicon.svg when real assets are ready. Then enable the smallest useful configuration changes, one at a time:

hugo.yaml
params:
  ui:
    theme_color: '#245f94'
    typography: system
    image_zoom: true
    share: [mastodon, linkedin, email, copy]

For custom local fonts, use params.ui.fonts for family names or declare font files in site CSS. For layout, sidebar, search, and component settings, consult the Configuration reference rather than copying the much larger configuration of oink.pgsty.com.

Layer 6: integrations

The Starter leaves repository actions, Giscus, Google Analytics, feedback, and sharing off or commented. Enable an integration only after all of its required facts are known:

  • repository links need the real owner, repository, and branch;
  • Giscus needs its repository/category names and immutable IDs;
  • Google Analytics needs a project-owned measurement ID;
  • feedback records structured gtag events only when analytics is present;
  • assistant links send the current URL to a third party and therefore require an explicit policy choice.

An incomplete optional block should remain commented. See Comments, Analytics and SEO, and Repository links for the operating boundary of each integration.

Build and deploy

Strict local build

Before enabling a hosting workflow:

hugo --cleanDestinationDir --gc --minify --environment production \
  --printPathWarnings --panicOnWarning

Commit hugo.yaml, go.mod, and go.sum; never commit generated public/, resources/, module caches, or a local module replacement.

GitHub Pages

The Starter already contains .github/workflows/github-pages.yaml. In Settings → Pages, select GitHub Actions as the source. A push to main builds with the pinned toolchain, asks GitHub for the correct project subpath, and publishes public/ through the Pages deployment API.

Cloudflare Pages

The supplied .github/workflows/cloudflare-pages.yaml uses Direct Upload. Create a Pages Direct Upload project, add CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN, then run the workflow manually once. Set the repository variable CLOUDFLARE_PAGES_ENABLED=true for automatic deploys, and CLOUDFLARE_SITE_URL when the canonical address is not the default pages.dev domain.

Use either Direct Upload or Cloudflare Git integration for one project, not both. The complete host comparison and baseURL rules are in Deploy.

Verify and remove samples

Before calling the site ready:

  1. Search for placeholders such as Project Name, example.org, OWNER, and PROJECT, then decide whether each remaining occurrence is intentional.
  2. Open every enabled language root and representative Docs, Blog, and Book pages on desktop and mobile.
  3. Confirm language switching lands on peers, not the home page.
  4. Test search, dark mode, one component, Markdown output, print, 404, canonical URLs, and repository actions.
  5. Check the deployed workflow and the public URL separately from the local build.

Delete the sample Book or Blog only after removing its top-menu root and any home-page card that links to it. A warning-strict rebuild after each whole surface is removed keeps failures attributable to one change.

Next

Use the Starter repository tour as a file-level map, then continue with Writing pages and Configuration. For an existing site that should not inherit the Starter’s content model, use From scratch.

2 - Starter repository tour

A file-level map of oink-starter — what owns identity, languages, home, content, navigation, brand, deployment, and the pinned theme.

This page describes the repository created from pgsty/oink-starter. It is not a tour of the much larger oink.pgsty.com documentation and regression repository. The theme source is not copied into either site: go.mod pins it as a Hugo Module, and Hugo stores the resolved source in the Go module cache.

Top-level map

oink-starter/

  • oink-starter/
    • hugo.yamlidentity, languages, outputs, parameters, module import
    • go.modsite module and exact OINK release
    • go.summodule checksums
    • examples/
      • hugo.single.yamlEnglish-only complete profile
      • hugo.bilingual.yamlEnglish + Chinese complete profile
    • data/
      • home/
        • en.yamlone compact landing page per language
        • zh.yaml
        • fr.yaml
    • content/
      • _index.mdlanguage home roots
      • _index.zh.md
      • _index.fr.md
      • docs/Introduction, Get Started, Tutorial, Reference
      • blog/posts, design records, release announcements
      • book/sequential tutorial about the Starter
    • assets/
      • icons/logo.svgprocessed project logo
    • static/
      • favicon.svgcopied unchanged to the site root
    • i18n/
      • fr.yamlStarter-specific French interface overrides
    • .github/workflows/
      • github-pages.yamlstrict build and GitHub Pages deployment
      • cloudflare-pages.yamlstrict build and Cloudflare Direct Upload
    • README.mdoperating summary for repository maintainers
    • LICENSEtemplate source license

Generated public/, resources/, .hugo_build.lock, and module caches are ignored build state, not source.

What to change first

Path Responsibility Initial action
hugo.yaml Identity, canonical URL, languages, outputs, theme features, optional integrations Change the two marked values; choose a language profile before other edits
data/home/ Home-page promise, cards, calls to action Rewrite every enabled language after one language is approved
content/ All reader-facing material Replace example leaves; keep a section root until deciding to remove that whole surface
assets/icons/logo.svg Processed logo Replace only with final artwork
static/favicon.svg Browser icon Replace together with the logo review
params.github_* in hugo.yaml Edit/history/new-page/issue links Uncomment only after the destination repository exists

What to keep

  • go.mod and go.sum: together they pin and verify OINK v1.0.0. Commit both.
  • The three Goldmark settings in hugo.yaml: native Steps, Cards, Fields, image attributes, and Book targets depend on them.
  • outputs: removing markdown, LLMS, or print intentionally removes the corresponding Markdown, agent-index, or print surfaces.
  • fetch-depth: 0 in workflows when enableGitInfo stays on: last-modified and contributor facts need repository history.
  • GOWORK: off and HUGO_MODULE_WORKSPACE: off in CI: a developer’s local workspace must not replace the published release being verified.

Optional surfaces

Docs, Blog, and Book are independent top-level surfaces. To remove one safely:

  1. delete its content/<surface>/ tree;
  2. remove any home-page card or link that targets it;
  3. confirm no other page links to it;
  4. run a warning-strict build and inspect the remaining top navigation.

Do not delete only translated section roots: that creates language-specific navigation and fallback behaviour that is difficult to distinguish from a mistake. Remove a surface in all enabled languages or document the asymmetry.

The two configuration profiles under examples/ are optional after the language decision. They are useful references, but the root hugo.yaml is the only active site configuration.

Content and navigation

Under Docs and Book, directory structure and weight form the sidebar and pager sequence. Top navigation comes from menus.main on section roots. A translated root repeats the same identifier, parent, and weight while translating visible labels.

The Starter intentionally demonstrates the Documentation System model:

  • Introduction explains what and why;
  • Get Started gets a new user to a result;
  • Tutorial teaches an end-to-end task;
  • Reference records exact supported behaviour.

Rename or reshape those sections for the project, but preserve the separation between learning paths rather than mixing every kind of answer into one tree.

Language model

English source files end in .md; Chinese and French peers end in .zh.md and .fr.md. Home data uses language keys under data/home/. The root profile declares the languages, their locale, order, and site description.

The single and bilingual profiles keep disabled languages declared. This is intentional: Hugo then recognizes the unused suffixes as translations instead of rendering several files onto one English URL. Copy a profile only before project-specific configuration begins; afterwards merge changes by hand.

Where OINK lives

Two files establish the module boundary:

hugo.yaml
module:
  imports:
    - path: github.com/pgsty/oink
  hugoVersion:
    extended: true
    min: '0.160.1'
go.mod
module github.com/OWNER/PROJECT-DOCS

go 1.27.0

require github.com/pgsty/oink v1.0.0

hugo mod graph shows the resolved version. Production follows the exact tag in go.mod; a local HUGO_MODULE_REPLACEMENTS value is a development override and must never be committed or treated as release proof.

Deployment files

The GitHub Pages workflow runs automatically on pushes to main; repository settings must select GitHub Actions as the Pages source. The Cloudflare workflow runs manually, or automatically only after the repository variable CLOUDFLARE_PAGES_ENABLED=true is set. Its required account ID and API token remain repository secrets.

Keep only the workflows for deployment paths you operate. Cloudflare Direct Upload and Cloudflare Git integration are alternative ownership models for the same project, not two gates to run together.

Safe customization order

  1. Prove the untouched preview.
  2. Change identity and select languages.
  3. Replace one home page and then its translations.
  4. Replace content and verify navigation.
  5. Change brand and reader features one group at a time.
  6. Enable complete external integrations.
  7. Run the strict production build.
  8. Deploy, then verify production independently.

Commit between layers when the repository is already yours. Small boundaries make a later regression or rollback attributable to one decision.

Verify

hugo mod graph | grep github.com/pgsty/oink
hugo --cleanDestinationDir --gc --minify --environment production \
  --printPathWarnings --panicOnWarning
git status --short

The module graph names the pinned release, the build emits no warning or error, and Git status contains source edits but no public/ or cache files. Then open the enabled language roots and one Docs, Blog, and Book route before moving to deployment.

3 - From scratch and other install methods

Build a minimal OINK site in an empty directory, and weigh the four install methods — Module, submodule, offline archive, pinned clone.

This is the manual alternative to the recommended OINK Starter. It builds a minimal site in an empty directory: a small hugo.yml plus one hugo mod get gives a single-language site you can preview. The cost is that the home page, example content, deployment workflow, and every component usage are yours to assemble.

An existing Hugo site needs no scaffolding: install the theme module, add the three Goldmark prerequisites (see Writing hugo.yml), and leave the content alone. For an existing Docsy site, see Upgrade.

The second half weighs four install methods: Hugo Module, Git submodule, offline archive, and pinned clone. For the current v1.0.0 release path, use Go 1.27 and Hugo Extended 0.165.0 unless an existing site deliberately targets the theme’s lower declared compatibility floor.

From an empty directory to the first page

  1. Create the skeleton and fetch the theme

    hugo new site --format yaml my-docs
    cd my-docs
    hugo mod init github.com/example/my-docs
    hugo mod get github.com/pgsty/oink@v1.0.0

    What follows hugo mod init is your own site’s module path, usually the repository address. hugo mod get writes go.mod and go.sum, and both are committed.

    The newest version number is on GitHub Releases; the v1.0.0 on this page is what this site currently pins. A production site pins a release tag rather than following main: @latest is a one-off resolution, not a version policy.

  2. Writing hugo.yml

    Rename the hugo.yaml that hugo new site generated to hugo.yml (Hugo accepts both; this documentation uses the latter throughout) and replace its contents with the following, which builds as it stands:

    hugo.yml
    title: Product Docs
    baseURL: https://docs.example.com/
    defaultContentLanguage: en
    # enableGitInfo: true        # the "last modified" time comes from git; run git init before enabling
    
    languages:
      en:
        label: English
        locale: en-US
        weight: 1
        title: Product Docs
        params:
          description: Everything about running Product in production
        menus:
          main:
            - { name: Docs, pageRef: /docs, weight: 20 }
            - { name: Blog, pageRef: /blog, weight: 50 }
    
    # The three Goldmark prerequisites: OINK's native Markdown components depend on them
    markup:
      goldmark:
        renderer:
          unsafe: true # allow inline HTML in content
        parser:
          attribute:
            block: true # attribute lines such as {.steps} {.cards} {caption=}
          wrapStandAloneImageWithinParagraph: false # only a block-level image can carry an attribute line
      highlight:
        noClasses: false # code colours follow light and dark mode
    
    params:
      offline_search: true
      github_repo: https://github.com/example/product-docs
      copyright:
        authors: '[Example Inc.](https://example.com/)'
        from_year: 2026
      ui:
        dark_mode: true
        sidebar_menu_foldable: true
        section_index: cards
    
    outputs:
      home: [HTML, markdown, LLMS]
      page: [HTML, markdown]
      section: [HTML, RSS, print, markdown]
    
    module:
      imports:
        - path: github.com/pgsty/oink
      hugoVersion:
        extended: true
        min: '0.160.1'

    What each of the five blocks governs:

    Block Governs Consequence of omitting it
    Top level + languages Site name, domain, languages and navbar menu A wrong baseURL sends every absolute link astray in production
    markup.goldmark The three component prerequisites An attribute line becomes a literal {.steps} in the prose
    params Search, repository links, shell switches Interactive features stay off; the theme does not decide for the site
    outputs The per-page .md, llms.txt and print pages No “Copy as Markdown” in the page menu, and no print view
    module References the theme and declares the Hugo floor The build cannot find the theme

    Mathematics additionally needs Goldmark’s passthrough extension; see Math. Every key’s full meaning and default is in Configuration.

  3. Write the first page

    Every top-level directory under content/ is a section, and the directory structure is the sidebar structure. A documentation section needs at least an _index.md:

    content/docs/_index.md
    ---
    title: Docs
    linkTitle: Docs
    description: Everything about running Product in production.
    weight: 20
    ---
    
    Start with [Install](/docs/install/).
    content/docs/install.md
    ---
    title: Install
    description: Install Product on a fresh machine.
    weight: 10
    ---
    
    ## Prerequisites {#prerequisites}
    
    > [!IMPORTANT]
    > Product needs PostgreSQL 18 or newer.
    
    ## Install {#install}
    
    ```bash
    curl -fsSL https://get.example.com | bash
    ```

    Write explicit {#id} anchors on headings: when a translation is added later, the two languages’ anchors have to correspond. How to write a page is in Writing pages.

  4. Preview

    hugo server

    Open http://localhost:1313/ and the sidebar shows Docs → Install. Edits hot-reload in milliseconds.

Other install methods

The steps above use a Hugo Module. The other three address particular constraints: network isolation, a platform that requires the build input to contain the whole theme tree, or an organization that reviews its own copy of the theme. Apart from hugo mod vendor, none of them creates a Go module, and the site references the theme with theme: oink rather than module.imports. The shared cost is that version resolution and integrity checking become your responsibility.

Hugo Module (recommended)

hugo mod init github.com/example/product-docs
hugo mod get github.com/pgsty/oink@v1.0.0
hugo.yml
module:
  imports:
    - path: github.com/pgsty/oink

The only method where Hugo resolves the version itself, verifies the checksum, and leaves an audit record in go.sum. hugo mod graph shows what actually resolved and hugo mod get -u upgrades. It needs Go on the machine.

Git submodule

Record an exact theme commit in the site repository:

git submodule add https://github.com/pgsty/oink.git themes/oink
git -C themes/oink fetch --tags
git -C themes/oink checkout v1.0.0
git add .gitmodules themes/oink
hugo.yml
theme: oink

CI must initialize the submodule before running Hugo, or themes/oink is an empty directory:

git submodule update --init --recursive

Offline archive

For network-isolated environments. Two paths, both prepared on a connected machine and carried in whole.

With hugo mod vendor, the resolved theme source is frozen into the site directory, and later builds need neither the network nor Go.

hugo mod vendor          # writes _vendor/, holding the theme's full source tree
tar czf my-docs.tgz .    # carry _vendor/ into the isolated environment with everything else

When _vendor/ exists Hugo prefers it (hugo mod graph prints +vendor), and module.imports in hugo.yml stays as it is. This step needs Go; the builds after it do not. Upgrading the theme means returning to a connected environment and running hugo mod get and hugo mod vendor again.

_vendor/ collects only the directories the theme mounts (assets, data, i18n, layouts, static) plus hugo.yaml and theme.toml. It does not include LICENSE, NOTICE or VENDOR.json. To redistribute that archive, take those three files from the theme repository as well.

With a tag source archive, no Go module is created; a version of the theme is simply unpacked into themes/oink/.

curl -L -o oink.tar.gz \
  https://github.com/pgsty/oink/archive/refs/tags/v1.0.0.tar.gz
mkdir -p themes/oink
tar xzf oink.tar.gz -C themes/oink --strip-components=1
hugo.yml
theme: oink

The theme repository’s root is the module root, so unpacking lands directly on layouts/, assets/, i18n/ and static/ with no further level to descend into. Redistribution must keep LICENSE, NOTICE and VENDOR.json; the last records each third-party runtime’s version, source, licence path and SHA-256, and is what an offline audit rests on.

When moving between machines, generate the archive and its checksum from an immutable tag on the connected side:

git clone --branch v1.0.0 --depth 1 \
  https://github.com/pgsty/oink.git oink
git -C oink archive --format=tar.gz --prefix=oink/ \
  --output=../oink-v1.0.0.tar.gz v1.0.0
shasum -a 256 oink-v1.0.0.tar.gz \
  > oink-v1.0.0.tar.gz.sha256

Carry the archive and its .sha256 into the isolated environment, verify, then unpack:

shasum -a 256 -c oink-v1.0.0.tar.gz.sha256
mkdir -p themes
tar -xzf oink-v1.0.0.tar.gz -C themes

An archive produced this way is your own artifact, not a project release. Whether a given tag’s release page carries an archive and a checksum file varies by release; verify the checksum independently when using a public attachment.

Before building offline, confirm the archive is complete. All eleven of these must be present:

themes/oink/

  • oink/
    • go.modmodule path declaration, used when resolving as a Hugo Module
    • hugo.yamltheme default parameters and the Hugo version floor
    • theme.tomltheme metadata, required by the theme: oink method
    • LICENSEApache-2.0
    • NOTICEupstream attribution; must be kept on redistribution
    • VENDOR.jsonthird-party runtime manifest: version, source, licence path, SHA-256
    • assets/SCSS, JS and the third-party runtimes shipped with the theme
    • layouts/templates, partials, shortcodes, render hooks
    • static/font files, published as is
    • i18n/32 interface language files
    • data/the SPDX licence table behind the page-end attribution line

Pinned clone

For a hosting platform that requires the build input to contain the whole theme tree:

git clone https://github.com/pgsty/oink.git themes/oink
git -C themes/oink checkout v1.0.0

The difference from a submodule is that the theme files enter your repository history directly, without the .gitmodules indirection. Record the commit that was finally resolved and the procedure for restoring it.

The four methods compared

Method Needs Go Version auditable Theme source in your repository Use when
Hugo Module Yes go.sum verifies automatically No The default
Git submodule No The repository records the commit By reference The theme source has to be in the repository
Offline archive No Checksums verified by hand Yes Network isolation
Pinned clone No You record it yourself Yes The platform requires a complete tree
A consuming site needs no front-end toolchain

Bootstrap, Font Awesome, the fonts, and the search and diagram runtimes all ship with the theme. A site needs no node_modules, no PostCSS, no RTLCSS and no CDN. Tutorials that install npm dependencies for a Docsy site describe upstream Docsy’s process and do not apply to OINK.

Developing against a local theme checkout

This section applies only when changing the theme and the site together. Clone the two repositories as siblings:

sibling directory layout
~/pgsty/
├── oink/            # the theme
└── product-docs/    # your site

Use the HUGO_MODULE_REPLACEMENTS environment variable to substitute the local checkout temporarily, leaving go.mod untouched:

cd ~/pgsty/product-docs
HUGO_MODULE_REPLACEMENTS='github.com/pgsty/oink -> ../oink' hugo server

The documentation site’s Makefile is an alias for exactly these commands, and make dev and make check expect the theme checkout at the sibling ../oink:

Makefile: as the documentation site writes it
build:
	hugo --cleanDestinationDir --minify

check:
	HUGO_MODULE_REPLACEMENTS='github.com/pgsty/oink -> $(abspath ../oink)' npm test

dev:
	HUGO_MODULE_REPLACEMENTS='github.com/pgsty/oink -> $(abspath ../oink)' hugo server --renderToMemory

A Go workspace (go work init plus HUGO_MODULE_WORKSPACE=go.work) is an equivalent alternative. Both apply to the local machine only: CI and production builds use the version in go.mod, and go.work is never committed.

Verify

hugo mod graph                                       # which theme version actually resolved
hugo --gc --minify --printPathWarnings --panicOnWarning

It passes when the build ends with Total in … and no WARN or ERROR. Then confirm:

  • /docs/ opens and the sidebar holds the page you wrote
  • The navbar has a search box that finds the heading you just wrote
  • The light/dark toggle is present, and code block colours follow it (which shows markup.highlight.noClasses: false took effect)
  • git status shows go.mod and go.sum, and no public/ or resources/