This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Get started

Build a bilingual Oink documentation site with Hugo Extended.

Oink is a Hugo theme whose complete browser runtime ships with the theme. A consumer site builds with Hugo Extended alone: no Node.js package installation, PostCSS step, CDN, or build-time remote asset download is part of the default path.

Choose a starting point

  • Hugo Module — recommended: import github.com/pgsty/oink in an existing or new Hugo site. See the Oink quick start.
  • Project site: use the independent pgsty/oink.pgsty.com repository as a complete bilingual configuration and regression reference.
  • Existing Docsy site: follow the migration guide to remove common overrides and the consumer npm asset pipeline without rewriting content.

Install the prerequisites

Install Git, Go, and Hugo Extended 0.160.1 or newer. See Before you begin for platform notes and verification commands.

Add Oink

From the site root:

hugo mod init github.com/example/product-docs
hugo mod get github.com/pgsty/oink@THEME_REF

Then import the theme in hugo.yaml:

module:
  imports:
    - path: github.com/pgsty/oink

Pin THEME_REF to a released tag or immutable commit and commit go.mod and go.sum.

Build contract

The same commands preview and build every supported module consumer:

hugo server --disableFastRender
hugo --gc --minify

Next steps

  1. Set the basic configuration.
  2. Add repository, copyright, logo, and menu values.
  3. Put translations side by side as page.md and page.zh.md.
  4. Add and customize content.
  5. Choose a deployment target.

1 - Use the Oink theme

Import the Oink Hugo Module or inspect the independent project site.

Oink separates the consuming site from the maintained theme. A site owns its content, brand assets, configuration, and business components; the theme owns the common shell, styles, browser runtimes, and reusable shortcodes.

Import github.com/pgsty/oink as a pinned Hugo Module. The independent pgsty/oink.pgsty.com repository demonstrates the full production contract with English and Chinese content, local search, dark mode, diagrams, API documentation, and component examples.

Experienced Hugo users can start from scratch. Existing Docsy sites should use the migration guide instead of recreating the shell by hand.

Theme source options

The preferred source is a released github.com/pgsty/oink module tag. A complete release archive, pinned Git submodule, or pinned clone also works. Read Other setup options for the trade-offs; production must never follow an unversioned branch.

Build contract

Whichever source option is selected, this command must build the site:

hugo --gc --minify

Node-based commands in the project-site repository are maintainers’ regression tooling, not prerequisites for a consuming site.

1.1 - Before you begin

Prerequisites for building an OINK site.

The consumer prerequisite is Hugo Extended. Git and Go are conditional on how the theme source is obtained.

Install Hugo Extended

Install version 0.160.1 or newer. The current validation baseline is 0.164.0. A release’s support matrix takes precedence when these values change.

Verify the selected binary:

hugo version

The output must contain extended. Standard Hugo cannot compile the theme’s SCSS. Use Hugo’s official installation guides for the platform and pin the same version in local development and CI.

Install Git when needed

Git is required to clone the site, use submodules, preserve .GitInfo, or fetch a theme checkout. Verify it with:

git --version

A site built from an already extracted offline archive can run Hugo without network access, but keeping the source in version control remains recommended.

Install Go only for Hugo Modules

Hugo’s module commands use Go. Install Go when the site imports the theme as a Hugo Module, then verify:

go version
hugo mod graph

A versioned archive, adjacent theme directory, or Git submodule does not require Go at site build time.

Do not install a frontend toolchain

OINK ships Bootstrap, Font Awesome, LTR and RTL CSS, fonts, search, and browser runtimes as local versioned assets. Consumer sites do not install Node.js, npm, PostCSS, Autoprefixer, or RTLCSS for the theme.

Node-based commands in the project-site repository are maintainer-only tools. The production consumer command is:

hugo --gc --minify

Check the complete distribution

For offline or air-gapped use, confirm that the theme archive contains go.mod, hugo.yaml, assets/, layouts/, static/, i18n/, LICENSE, NOTICE, and VENDOR.json. Install Hugo Extended before entering the isolated environment, then run the same build command with network access disabled.

What’s next?

1.2 - Inspect the bilingual project site

Use the independent Oink project site as a complete reference.

The independent pgsty/oink.pgsty.com repository is the complete bilingual example and regression site. It is intentionally more comprehensive than a starter: use it as a reference, then keep only the content and configuration your product needs.

Clone the project site

After the Oink theme has a public release, clone and build the site directly:

git clone https://github.com/pgsty/oink.pgsty.com.git product-docs
cd product-docs
hugo --gc --minify

The committed go.mod pins github.com/pgsty/oink. For local theme development, clone the theme as a sibling and use the workspace commands documented in the Oink quick start.

Run the site checks

Hugo alone builds the site. Node.js is used only for the project site’s formatting, link, translation, and regression checks:

npm install
npm test

Open the generated site and check both English and Chinese pages. Use the language switcher from a translated detail page, not only from the home page.

Replace the example identity

Edit hugo.yaml and the files under config/, then replace:

  • site and per-language titles and descriptions;
  • baseURL;
  • repository and branch URLs;
  • copyright holder and starting year;
  • logo and brand assets;
  • English and Chinese menu labels.

Do not create an oink.* parameter namespace. Use Hugo’s language, menu, module, output, and markup settings plus the documented theme parameters.

Replace the example content

Keep each translation pair together:

content/docs/getting-started.md
content/docs/getting-started.zh.md

Delete historical and regression content that the product does not need. Remove an example asset only after no page references it.

For translated headings, use the English rendered ID explicitly:

## Configure search
## 配置搜索 {#configure-search}

Put the new site in version control

Change the module path, repository metadata, and remote before publishing a derived site. Keep the Oink version pinned in go.mod. Do not commit generated public/ output unless the hosting workflow explicitly requires it.

What’s next?

1.3 - Create a new site: start from scratch

Create a minimal bilingual OINK site without a frontend toolchain.

The independent bilingual project site is a useful reference. Use this procedure to create a smaller site with its own content structure.

Create the site skeleton

Run:

hugo new site --format yaml my-new-site
cd my-new-site

Initialize the site module and pin Oink:

hugo mod init github.com/example/my-new-site
hugo mod get github.com/pgsty/oink@THEME_REF

Add minimum configuration

Use this as hugo.yaml:

title: Product Docs
baseURL: https://docs.example.com/
defaultContentLanguage: en

languages:
  en:
    label: English
    locale: en-US
    weight: 1
    menus:
      main:
        - { name: Docs, pageRef: /docs, weight: 10 }
        - { name: Blog, pageRef: /blog, weight: 20 }
  zh:
    label: 简体中文
    locale: zh-CN
    weight: 2
    menus:
      main:
        - { name: 文档, pageRef: /docs, weight: 10 }
        - { name: 博客, pageRef: /blog, weight: 20 }

markup:
  goldmark:
    renderer:
      unsafe: true
  highlight:
    noClasses: false

params:
  offlineSearch: true
  ui:
    showLightDarkModeMenu: true
    sidebar_menu_foldable: true

module:
  imports:
    - path: github.com/pgsty/oink
  hugoVersion:
    extended: true
    min: 0.160.1

Commit go.mod and go.sum. Do not add npm mounts or a PostCSS pipeline.

Add bilingual content

Create these files:

content/
├── _index.md
├── _index.zh.md
├── docs/
│   ├── _index.md
│   ├── _index.zh.md
│   ├── getting-started.md
│   └── getting-started.zh.md
└── blog/
    ├── _index.md
    └── _index.zh.md

Every page needs front matter. For example, content/docs/getting-started.md:

---
title: Getting started
weight: 10
---

## Install {#install}

Install the product.

Its getting-started.zh.md translation keeps the explicit heading ID:

---
title: 开始使用
weight: 10
---

## 安装 {#install}

安装产品。

Using the same explicit ID in both examples is harmless and makes the intended cross-language contract visible. In a translated existing page, copy the ID from the English rendered HTML.

Preview and build

Run the development server:

hugo server --disableFastRender

Then verify the production build separately:

hugo --gc --minify

Check /docs/, /zh/docs/, the language selector, local search indexes, and the browser console before adding custom layouts.

Add features incrementally

Copy logo and brand assets first, then add repository links and menus. Add diagrams, API documentation, and content components only on pages that need them; OINK will publish their local runtimes on demand.

If a site needs a business-specific shortcode, keep it under the site’s own layouts/_shortcodes/. Move it into the theme only after its interface is free of site assumptions and multiple sites can reuse it.

What’s next?

2 - Other setup options

Use an OINK archive, Git checkout, or Hugo Module.

The recommended installation uses the github.com/pgsty/oink Hugo Module. The following options change how Hugo obtains the same theme source; they do not change content or the Hugo-only build command.

Prerequisites

Every option requires Hugo Extended 0.160.1 or newer. Git options require Git, and Hugo Modules require Go. None of the options requires Node.js, npm, PostCSS, or Autoprefixer for the consuming site.

Option 1: complete release archive

The complete offline archive contains the theme, local browser runtimes, fonts, licenses, notices, vendor manifest, and checksums. It is the preferred input for air-gapped builds and the simplest way to preserve an exact distribution.

Extract the theme under the site’s themes/ directory:

site/
├── hugo.yaml
└── themes/
    └── oink/

Configure:

theme: oink

Verify the archive checksum before extracting it. Use only an archive attached to an explicit release, not a locally assembled file presented as a published distribution.

Option 2: Git submodule

A submodule records the exact OINK repository 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 THEME_REF
git add .gitmodules themes/oink
git commit -m "Add OINK theme at THEME_REF"

Configure the nested theme path:

theme: oink

CI must initialize submodules before running Hugo. Pin THEME_REF to a release tag or immutable commit; do not leave production on main.

Option 3: pinned Git clone

A clone works when the hosting platform requires the complete theme tree in the build input or when the site vendors a reviewed copy:

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

Use the same theme: oink setting. Record the resolved commit and the process that restores the clone. If the files are committed into the site repository, preserve OINK’s LICENSE, NOTICE, and VENDOR.json.

OINK is not distributed as an npm package. Existing Docsy npm consumers should follow the npm migration guide.

Option 4: Hugo Module

Pin the public module to a release tag or immutable commit:

hugo mod get github.com/pgsty/oink@THEME_REF
hugo mod tidy

Import it in hugo.yaml:

module:
  imports:
    - path: github.com/pgsty/oink

For local theme development, use an ignored Go workspace that includes the site module and a sibling OINK checkout.

Preview and verify

All source options use the same commands:

hugo server --disableFastRender
hugo --gc --minify

Verify that a clean production build succeeds with no node_modules directory, that local assets resolve under the configured baseURL, and that both English and Chinese pages and search indexes are present.

See Update OINK for version changes and override review.

3 - Run OINK in a container

Build and preview an OINK site with a Hugo Extended container.

A container is optional: OINK itself only needs Hugo Extended. Use a container when the team wants a pinned tool image or does not install Hugo on developer workstations.

Create the Hugo image

The following Dockerfile installs the currently validated Hugo Extended version from its release package. Keep the version aligned with the theme’s support matrix.

FROM debian:bookworm-slim

ARG HUGO_VERSION=0.164.0
ARG TARGETARCH

RUN apt-get update \
    && apt-get install -y --no-install-recommends ca-certificates curl git \
    && curl -L -o /tmp/hugo.deb \
      "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-${TARGETARCH}.deb" \
    && apt-get install -y /tmp/hugo.deb \
    && rm -rf /var/lib/apt/lists/* /tmp/hugo.deb

WORKDIR /src
EXPOSE 1313
ENTRYPOINT ["hugo"]
CMD ["server", "--bind", "0.0.0.0", "--disableFastRender"]

Build it from the site root:

docker build -t oink-hugo .

The image build downloads Hugo. For an air-gapped environment, mirror the base image and Hugo package in advance or use OINK’s complete offline distribution with an approved internal image.

Preview the site

Mount the complete site source, including its adjacent or vendored theme:

docker run --rm -it \
  -p 1313:1313 \
  -v "$PWD:/src" \
  oink-hugo

Open http://localhost:1313/. Changes on the host are visible to Hugo’s live reload process inside the container.

Run a production build

Override the default server command:

docker run --rm \
  -v "$PWD:/src" \
  oink-hugo --gc --minify

The generated site is written to public/ in the mounted source directory. Ensure the container user can write there; in a shared environment, run with a mapped user ID or fix ownership according to local policy.

No Node.js, npm, PostCSS, or remote browser asset step belongs in this image.

4 - Basic site configuration

Configure an OINK site, languages, navigation, and local features.

Hugo reads site-wide settings from hugo.yaml, hugo.toml, or hugo.json. The The Oink project site uses YAML because multilingual menus and theme options remain easy to scan and review.

Minimum configuration

The following excerpt shows the important structure for the Hugo Module.

title: Product Documentation
baseURL: https://docs.example.com/
defaultContentLanguage: en

languages:
  en:
    label: English
    locale: en-US
    weight: 1
    title: Product Documentation
    menus:
      main:
        - name: Docs
          pageRef: /docs
          weight: 10
        - name: Blog
          pageRef: /blog
          weight: 20
  zh:
    label: 简体中文
    locale: zh-CN
    weight: 2
    title: 产品文档
    menus:
      main:
        - name: 文档
          pageRef: /docs
          weight: 10
        - name: 博客
          pageRef: /blog
          weight: 20

markup:
  goldmark:
    renderer:
      unsafe: true
  highlight:
    noClasses: false

params:
  offlineSearch: true
  github_repo: https://github.com/example/product-docs
  github_branch: main
  copyright:
    authors: Example Authors
    from_year: 2026
  ui:
    showLightDarkModeMenu: true
    sidebar_menu_foldable: true
    breadcrumb_disable: false

module:
  imports:
    - path: github.com/pgsty/oink
  hugoVersion:
    extended: true
    min: 0.160.1

English has weight 1 and is the default language; Simplified Chinese has weight 2; additional languages follow. The language selector uses this order when a click cycles to the next language and when the full hover menu is rendered.

Content translations

Put translations beside each other:

content/
├── _index.md
├── _index.zh.md
├── docs/
│   ├── _index.md
│   ├── _index.zh.md
│   ├── install.md
│   └── install.zh.md
└── blog/
    ├── release.md
    └── release.zh.md

Keep route-affecting metadata aligned. Translate titles, descriptions, menu labels, summaries, tags, image alternatives, and visible shortcode strings. Use the English rendered heading ID as an explicit ID on each translated heading so that fragments remain stable across languages.

Local search and browser resources

offlineSearch: true enables the theme’s same-origin Lunr index and CJK fallback. The index is generated per language. Do not configure a public search service unless the site intentionally accepts that network dependency.

Mermaid, KaTeX, Markmap, Swagger UI, Redoc, Asciinema, ECharts, and Infographic are provided locally and loaded per page. PlantUML and Draw.io are service-based exceptions: configure an approved endpoint explicitly or keep them disabled.

Set title, per-language titles, params.logo, repository URLs, copyright, and menus at the site layer. OINK does not add an oink.* configuration tree; it uses Hugo and compatible Docsy parameter locations.

Repository metadata enables edit, view, issue, and age information on content pages. Keep github_repo, github_project_repo, github_branch, and github_subdir consistent with the source layout.

Production defaults

  • Use a real production baseURL, including any subpath.
  • Keep online analytics, comments, Google CSE, Algolia, and remote embeds off unless they are an explicit product choice.
  • Pin Hugo Extended and the theme release in CI.
  • Run hugo --gc --minify as the production command.
  • Keep LICENSE, NOTICE, and the vendor manifest in redistributed archives.

See the project site’s complete hugo.yaml for a buildable reference.

5 - Troubleshooting and known issues

Diagnose OINK installation, build, language, search, and platform issues.

Start diagnosis from a clean production build:

hugo --gc --minify --logLevel info

The consumer command should not invoke npm, PostCSS, Autoprefixer, or download theme browser assets.

Build issues

Hugo is not Extended or is too old

Run hugo version. The output must include extended, and the version must be at least 0.160.1. If a shell, editor, CI runner, or container still selects an older binary, inspect its PATH and pinned tool configuration rather than installing another copy blindly.

The theme cannot be found

An error such as module "github.com/pgsty/oink" not found means that Hugo cannot resolve the configured theme. Check the selected installation mode:

  • for a Git checkout, the theme name and directory path must agree;
  • for a Hugo module, run hugo mod graph and inspect go.mod, go.sum, and any configured Hugo workspace or replacement;
  • for a CI checkout, initialize the pinned submodule or restore the complete release archive before running Hugo.

A local browser asset is missing

Do not fix a missing Bootstrap, Font Awesome, Lunr, Mermaid, or other OINK asset by adding a CDN URL. Confirm that the distribution is complete and contains assets/third_party/, assets/js/third_party/, static/webfonts/, and VENDOR.json. Re-extract or re-fetch the same pinned release if files are missing.

A translated page does not appear

Check all four conditions:

  1. languages.zh exists and has a weight in hugo.yaml.
  2. The file is named page.zh.md, including lowercase zh.
  3. The translated front matter does not set draft: true or a future date.
  4. Route-affecting metadata matches the source unless a different route is intentional.

The language selector links to a page translation when Hugo reports one; otherwise it deliberately falls back to the target-language home page.

Translated heading text normally generates a different automatic ID. Add the English rendered ID explicitly to the translated heading:

## 安装 {#installation}

Do not infer IDs for headings containing shortcodes or inline HTML. Inspect the English rendered HTML, then compare the English and Chinese heading ID lists.

Search issues

With offlineSearch: true, each language produces its own search index. Check that offline-search-index.en.json and offline-search-index.zh.json exist in the output and that the browser requests them from the site’s base URL. A wrong baseURL is a common cause of missing indexes on subpath deployments.

Chinese tokenization uses the theme’s CJK fallback. If results are empty, first verify that the Chinese page content is present in the Chinese index rather than changing the tokenizer.

Platform issues

macOS reports too many open files

Large live-preview trees can exceed the shell’s open-file limit. Inspect the current limit with ulimit -n and raise it temporarily for the current shell if local policy permits. Prefer excluding generated or unrelated directories from the watched tree before applying a machine-wide limit change.

Windows Subsystem for Linux is slow or misses changes

Run Hugo against a Linux filesystem path rather than a Windows-mounted path. Cross-filesystem notification and permission behavior can make live reload slow or unreliable.

Diagnostic checklist

  • Reproduce with the exact pinned Hugo Extended version.
  • Remove stale public/ and resources/ output through the project’s normal clean command, then rebuild.
  • Compare development and production configuration layers.
  • Check the first build error, not only the final cascading message.
  • Test a minimal page to separate theme behavior from site overrides.
  • Re-enable site overrides and content components in small groups.
  • Inspect the browser console and network log for the failing page.