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

Return to the regular view of this page.

OINK

Product boundaries, architecture, and operating model

Oink is an independent, local-first Hugo documentation theme derived from Docsy. It keeps Docsy’s mature content model while making one implementation the canonical product: a documentation shell, a Hugo-only consumer build, local browser runtimes, multilingual infrastructure, and reusable content components.

The public Hugo Module is github.com/pgsty/oink. Documentation and regression content live separately in github.com/pgsty/oink.pgsty.com.

Product contract

One canonical theme

Oink is not a skin layered over another Docsy installation. There is no oink.enabled switch, no params.oink.* namespace, and no second visual implementation to synchronize. The layouts and assets at the theme repository root are the product.

Use Hugo’s native language, module, menu, output, and markup settings; use existing Docsy parameters where their semantics still fit; add a narrowly scoped parameter only when the theme needs a real choice.

Hugo-only consumer builds

After a site imports the module, its production build is:

hugo --gc --minify

A consuming site does not install Node.js, npm, PostCSS, Autoprefixer, or browser packages. Maintainer tooling in the project-site repository is outside the consumer build contract.

Local-first by default

Bootstrap, Font Awesome, webfonts, local search, diagram and API-documentation runtimes, and Oink content components ship with the theme. Assets are served from the generated site and, where practical, loaded only on pages that use them.

Authors can still link to the web, embed remote media, enable hosted services, or configure PlantUML and Diagrams.net endpoints. Those boundaries are explicit; Oink does not silently select public endpoints for theme-owned features.

Multilingual as infrastructure

Language behavior is derived from Hugo’s configured languages and page translations. Oink emits language, direction, canonical, hreflang, and Open Graph locale metadata and supports colocated translations such as .md and .zh.md.

What ships

  • responsive docs and blog shells, navigation, search, print, dark mode, and mobile behavior;
  • local Mermaid, KaTeX, Markmap, Swagger UI, Redoc, Asciinema, ECharts, and Infographic runtimes;
  • details, tabs, cards, navigation cards, document cards, and carousels;
  • translations and a versioned VENDOR.json with source, license, and checksum metadata;
  • the Hugo module declaration, Apache-2.0 license, and required attribution.

What does not ship

The theme repository does not contain the project website, generated public/ output, npm workspaces, product-specific widgets, or deployment configuration. Those responsibilities stay in the consuming site or the separate project-site repository.

Production sites should pin a released tag or immutable commit rather than follow main.

Repositories

Repository Purpose
pgsty/oink Published theme and Hugo Module
pgsty/oink.pgsty.com Documentation, examples, tests, and deployment

For local theme development, clone them as siblings and connect them with an ignored Go workspace.

Project status

Hugo Extended 0.164.0 is the current validation baseline, and the theme declares 0.160.1 as its minimum. A successful local build does not by itself prove that a tag, hosted site, or downstream deployment exists.

The project preserves Docsy’s Apache-2.0 history and attribution. Source and offline distributions must retain LICENSE, NOTICE, and applicable third-party notices.

Next steps

  1. Install the Hugo Module.
  2. Read the architecture and local-first model.
  3. Review components and configuration.
  4. Choose a deployment and follow the release checklist.
  5. For an existing Docsy site, start with the migration guide.

1 - Getting started

Add Oink to a site as a Hugo Module

Oink is published as the Hugo Module github.com/pgsty/oink. A consuming site builds with Hugo Extended alone; Node.js, npm, PostCSS, and CDN-hosted browser packages are not part of the build contract.

Prerequisites

Install Git, Go, and Hugo Extended 0.160.1 or newer. The project site currently validates with 0.164.0:

git --version
go version
hugo version

The Hugo version output must include extended.

Add the module

From your Hugo site root, initialize a module if the site does not already have one, then pin an Oink release:

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

Replace THEME_REF with a published tag such as v0.16.0 or an immutable commit. Add the import to hugo.yaml:

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

Commit the resulting go.mod and go.sum. Do not run production builds against an unpinned branch.

Preview the site

Start an editing server:

hugo server --disableFastRender

Create a production artifact with:

hugo --gc --minify

Oink ships Bootstrap, Font Awesome, fonts, search, diagrams, API documentation runtimes, and its content components. A consuming site does not need a node_modules directory.

Develop against a local checkout

Clone the theme and site as siblings, then use a local Go workspace:

~/pgsty/
├── oink/
└── product-docs/
cd ~/pgsty/product-docs
go work init .
go work edit -replace=github.com/pgsty/oink=../oink
export HUGO_MODULE_WORKSPACE=go.work
hugo server

Keep go.work out of version control. The committed go.mod remains pinned to the public module; the workspace substitutes the sibling checkout only on your machine.

Add bilingual content

Create the English page first:

content/docs/operations.md

Then add its translation beside it:

content/docs/operations.zh.md

Keep front matter identifiers, code, commands, parameter names, and link targets semantically aligned. Translate reader-facing prose. For stable cross-language deep links, preserve the English heading ID explicitly in the Chinese heading:

## 故障恢复 {#failure-recovery}

Configure the minimum site

The essential configuration is small:

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

languages:
  en:
    label: English
    locale: en-US
    weight: 1
  zh:
    label: 简体中文
    locale: zh-CN
    weight: 2

params:
  logo: icons/logo.svg
  offlineSearch: true

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

Add menus, outputs, markup extensions, repository links, and optional features as the site grows. See Configuration for the supported model.

Validate before publishing

At minimum:

  1. build from a clean checkout with the committed module files;
  2. run hugo --gc --minify with the pinned Hugo Extended version;
  3. browse representative English and Chinese pages;
  4. verify language switching, search, mobile navigation, dark mode, and print;
  5. inspect browser network requests if the site promises offline operation.

These checks establish a build artifact. Publishing that artifact and verifying the hosted URL are separate deployment steps.

2 - Architecture

How Oink turns content and local assets into a documentation site

Oink is a direct Hugo theme, not an application server or a runtime wrapper around Docsy. Hugo resolves content, configuration, layouts, and assets at build time, then emits a static site for any ordinary file host.

System boundary

flowchart LR
  C[Site content] --> H[Hugo Extended]
  G[Hugo configuration] --> H
  T[Oink Hugo Module] --> H
  V[Committed third-party assets] --> T
  H --> P[Static public directory]
  P --> B[Browser]

The consumer boundary starts with a site plus the resolved theme module and ends with Hugo’s static output. No JavaScript package manager, CSS postprocessor executable, or remote asset download is required in that path.

JavaScript still runs in the browser for interactive features. “Hugo-only” describes the build dependency, not a JavaScript-free user interface.

Repository boundary

Theme repository

github.com/pgsty/oink is the published Hugo Module. Its root contains the canonical layouts, partials, shortcodes, SCSS, JavaScript, fonts, icons, browser runtimes, translations, go.mod, and hugo.yaml. VENDOR.json records the bundled third-party assets.

The repository contains no project website or npm workspace. Root metadata such as README.md, LICENSE, NOTICE, theme.toml, and the vendor manifest is part of distributing and attributing the theme.

Project site repository

github.com/pgsty/oink.pgsty.com contains the documentation, bilingual examples, regression pages, site-specific layouts and assets, npm-based site tests, and deployment configuration. It imports the public theme module in hugo.yaml and pins its version in go.mod.

For local cross-repository development, an ignored go.work substitutes a sibling theme checkout. No relative filesystem replacement is committed to the site module.

Build pipeline

Hugo combines four classes of input:

  1. page bundles and Markdown content from the consuming site;
  2. native Hugo configuration and supported theme parameters;
  3. theme templates, translations, SCSS, and JavaScript;
  4. committed static or Hugo Asset resources.

Hugo compiles SCSS with its embedded pipeline, bundles page JavaScript, minifies production resources, fingerprints eligible outputs, and rewrites relative URLs for the configured baseURL. Oink does not invoke Hugo’s postCSS pipe.

The final public/ directory contains HTML, CSS, JavaScript, fonts, search indexes, feeds, sitemaps, and copied static files. It can be deployed without the source tree.

Page shell

The canonical page shell is assembled from small partials:

  • a global navbar and responsive sub-navigation;
  • language and color-mode controls;
  • a resizable, foldable documentation sidebar;
  • breadcrumbs, table of contents, reading metadata, feedback, and repository links where configured;
  • a shared footer and print layouts.

Normal Hugo lookup remains available for site-specific extensions. Override the narrowest partial possible instead of copying baseof.html or the entire shell.

Conditional runtime loading

Content shortcodes record feature use in the page store. Asset partials inspect those markers and include the corresponding local runtime at most once:

flowchart TD
  S[Shortcode renders] --> M[Set page feature marker]
  M --> A[Asset assembly]
  A --> Q{Feature used?}
  Q -- Yes --> L[Bundle local runtime once]
  Q -- No --> O[Omit runtime]

This keeps a plain article free of ECharts, Asciinema, or Infographic code while allowing multiple component instances on a feature page.

Multilingual routing

Oink delegates language identity to Hugo. The selector uses each page’s .Translations and the site’s configured languages, ordered by weight. Missing translations fall back to the target-language home page. The same data drives canonical and alternate metadata.

Security boundaries

Oink treats authored data and authored executable code differently:

  • structured ECharts options are parsed as JSON or YAML and safely serialized;
  • JavaScript in ECharts is rejected unless an explicit unsafe migration switch is enabled;
  • component identifiers and configuration are generated by templates rather than unescaped HTML strings;
  • hosted search, analytics, comments, remote media, and service endpoints remain explicit site decisions.

Goldmark’s unsafe setting permits trusted project authors to use inline HTML; it is not a sanitizer for untrusted submissions.

Upstream maintenance

Oink preserves Docsy’s source history and Apache-2.0 obligations. Upstream changes are classified as applicable, superseded by an intentional Oink difference, or unrelated. Applicable changes are ported into the canonical implementation without recreating an upstream-versus-brand runtime switch.

Extension boundary

Put an implementation in the theme when it is broadly reusable, has a stable content API, and can own its assets and accessibility behavior. Keep it in the site when it embeds product data, pricing, catalog assumptions, or a one-off landing-page structure.

3 - Local-first operation

Build and browse core OINK features without hidden network dependencies

OINK’s local-first rule is simple: a feature owned by the theme must not silently depend on a public CDN, a build-time download, or an unconfigured public service. A complete distribution can be built and its core pages browsed inside a network-isolated environment.

What local-first covers

The theme serves these dependencies from the generated site:

Capability Local delivery
Shell and responsive UI Bootstrap and OINK CSS/JavaScript
Icons and fonts Font Awesome, Open Sans, Chakra Petch, IBM Plex Mono
Search Lunr plus a CJK substring fallback and per-language indexes
Diagrams and formulae Mermaid, KaTeX, and Markmap
API documentation Swagger UI and Redoc
Rich content Asciinema, ECharts, Infographic, and carousel runtime

Assets are committed under assets/ or static/. Hugo publishes them under the site’s baseURL, including subpath deployments.

What local-first does not cover

OINK cannot make arbitrary authored content offline. The following remain explicit network choices:

  • external links, remote images, video, iframes, and API specifications;
  • hosted search such as Algolia or Google CSE;
  • analytics, comments, identity providers, and other SaaS integrations;
  • PlantUML or Diagrams.net when an author chooses a remote renderer.

A page using one of these features can still be valid, but the site should not claim that page is fully available offline.

Service-backed diagrams

PlantUML and Diagrams.net differ from browser libraries: their normal workflows depend on a rendering or editing service. OINK therefore has no implicit public endpoint.

Enabling PlantUML without params.plantuml.svg_image_url, or Diagrams.net without params.drawio.drawio_server, fails the build with an actionable message. Configure a controlled local endpoint, publish a pre-rendered image, or make a deliberate remote-service choice:

params:
  plantuml:
    enable: true
    svg: true
    svg_image_url: https://diagrams.internal.example/plantuml/svg/
  drawio:
    enable: true
    drawio_server: https://diagrams.internal.example/

The OINK documentation regression site explicitly configures public demo servers so inherited diagram examples continue to render. That sample-site choice is not the theme default and should not be copied into an air-gap site.

Set:

params:
  offlineSearch: true

Hugo generates a search index for each language. The browser uses local Lunr search for Latin-script queries and a local substring fallback for CJK text. No query leaves the site.

Keep search useful by writing descriptive titles and summaries, setting the correct page language, and excluding generated or sensitive pages that should not enter a public client-side index. A local index is downloadable by every visitor and is not an access-control mechanism.

Per-page assets

OINK does not place every runtime on every page. Mermaid, KaTeX, Markmap, Swagger UI, Redoc, Asciinema, ECharts, Infographic, and the carousel are selected from page feature markers. A page that does not use a component does not receive that component’s runtime.

When a page contains several instances of the same component, the runtime is still included once. Production resources are fingerprinted where the Hugo pipeline permits it, which supports integrity metadata and long-lived caching.

Third-party provenance

VENDOR.json is the machine-readable inventory for bundled dependencies. For each dependency it records:

  • name and pinned version;
  • original source;
  • applicable license files;
  • selected artifact paths and SHA-256 values;
  • the maintainer update procedure.

The theme retains the corresponding license files beside vendor assets. Updating a runtime means refreshing the artifact, its license and notice material, its checksum, and its tests as one reviewable change.

Obtain an offline archive

Use the versioned theme archive and checksum attached to an Oink release. After transferring both files into the isolated environment:

shasum -a 256 -c oink-vX.Y.Z.tar.gz.sha256
tar -xzf oink-vX.Y.Z.tar.gz
mkdir -p product-docs/themes
mv oink-vX.Y.Z product-docs/themes/oink

Configure the isolated site to use the extracted conventional theme:

theme: oink

The archive must include go.mod, hugo.yaml, layouts, assets, static files, translations, LICENSE, NOTICE, and VENDOR.json. Inspect it before relying on it in a disconnected build.

Verify an isolated site

A meaningful air-gap acceptance test covers both build time and browser time:

  1. start with the verified theme archive and an empty Hugo cache;
  2. block outbound HTTP, HTTPS, and Go module proxies;
  3. run the production Hugo command;
  4. browse English and Chinese pages from the generated output;
  5. exercise search, dark mode, diagrams, API docs, and content components;
  6. inspect every HTML and CSS subresource URL for unexpected remote origins.

The project-site regression suite performs these checks against a local theme candidate. A successful test proves only the tested commit and environment; repeat it for every release candidate and after bundled dependency updates.

Content security policy

Local assets make a strict Content Security Policy practical, but OINK does not invent one universal policy for every site. Inline author HTML, ECharts unsafe mode, analytics, remote specifications, and custom integrations can all change the required directives.

Start with the smallest policy that supports the site’s reviewed features. Keep ECharts in structured-data mode, avoid arbitrary inline scripts, and add remote origins only for integrations that the site deliberately enables.

4 - Content components

Local, reusable components added by OINK

OINK promotes the content components that proved reusable across PGSTY sites into the theme. Each component has a stable authoring API, unique instance IDs, local assets, and a defined safety boundary. Site-specific data widgets remain outside the theme.

Loading model

Interactive shortcodes mark the features used by a page. OINK then adds each required stylesheet or runtime once, even if the page has several component instances. A plain page does not download component code it never uses.

Relative asset and link parameters pass through Hugo’s URL handling, so they remain correct under a subpath baseURL. Component markup also has print, dark-mode, mobile, keyboard, and reduced-motion behavior where applicable.

Asciinema

Use asciinema for a terminal recording stored as a local .cast file:

{{< asciinema
  file="oink/demo.cast"
  speed="1.5"
  markers="0:Start,1:Done"
>}}

file is required and can also be the first positional argument. Supported options are theme, fit (width, height, both, or none), autoplay, loop, preload, speed, startAt, poster, cols, rows, idleTimeLimit, pauseOnMarkers, and comma-separated markers.

Keep cast files local for offline use. A remote URL is accepted only when the author explicitly supplies it.

ECharts

The safe default accepts JSON or YAML and serializes the parsed value into an application/json element:

{{< echarts height="280px" >}}
xAxis: { type: category, data: [Source, Build, Publish] }
yAxis: { type: value }
series: [{ type: bar, data: [1, 2, 3] }]
{{< /echarts >}}

height defaults to 400px and must use a safe CSS length unit. theme selects an ECharts theme, and full=true removes the normal content-width constraint.

Legacy pages may contain a fenced JavaScript block and $fn:name references. OINK rejects that executable form unless the shortcode sets unsafe=true or the site temporarily enables:

params:
  content:
    echarts_unsafe: true

Use the switch only during a reviewed migration. New charts should remain in the structured JSON/YAML mode.

Infographic

infographic renders the AntV Infographic DSL locally:

{{< infographic >}}
infographic list-row-simple-horizontal-arrow
data
  items
    - label Source
      desc Markdown and configuration
    - label Build
      desc Hugo Extended
    - label Publish
      desc Static files
{{< /infographic >}}

height accepts auto or a safe CSS length; full=true removes the normal content-width constraint. The DSL is serialized as data rather than inserted as an executable script.

doc-card and nav-card share one card implementation. doc-cards and nav-cards create responsive groups of one to four columns. The aliases let an existing site’s content keep its most descriptive name without duplicating markup or styles.

{{< nav-cards cols="3" >}}
  {{< nav-card
    title="Architecture"
    link="/docs/oink/architecture/"
    icon="fa-solid fa-diagram-project"
    desc="Understand the build and runtime boundaries."
  >}}
  {{< nav-card
    title="Deployment"
    link="/docs/oink/deployment/"
    badge="Hugo-only"
  >}}Publish the static output.{{< /nav-card >}}
{{< /nav-cards >}}

A card accepts title, link, image, alt, icon, desc, accent, and badge. Its body can contain Markdown links. Tokens such as {version} in desc resolve from site parameters when a matching value exists.

Wrap document cards in doc-carousel to create an accessible horizontal carousel:

{{< doc-carousel label="OINK workflow" >}}
  {{< doc-card title="Write" >}}Create paired content.{{< /doc-card >}}
  {{< doc-card title="Build" >}}Run Hugo Extended.{{< /doc-card >}}
  {{< doc-card title="Verify" >}}Inspect the static site.{{< /doc-card >}}
{{< /doc-carousel >}}

label supplies the carousel’s accessible name. Arrow keys and visible previous/next controls navigate the track; reduced-motion preferences disable unnecessary animation.

Details

details emits native details and summary elements:

{{% details title="Why Hugo-only?" closed="false" %}}
Committed browser assets keep the consuming build reproducible.
{{% /details %}}
Why Hugo-only?
Committed browser assets keep the consuming build reproducible.

title sets the summary. The block is closed by default; set closed=false to render it open.

Tabs

OINK keeps Docsy’s tabpane and tab authoring model while preserving selected=true and whitespace behavior used by imported sites:

{{< tabpane text=true >}}
  {{< tab header="Local" selected=true >}}
  Build with the complete local theme.
  {{< /tab >}}
  {{< tab header="Cloudflare" >}}
  Run the same Hugo command from the source branch.
  {{< /tab >}}
{{< /tabpane >}}
Build with the complete local theme.
Run the same Hugo command from the source branch.

Use text=true for Markdown content; otherwise tabs are syntax-highlighted code. Tab panes also support language-aware persistence, disabled tabs, and right-aligned entries. Generated tab and panel IDs have matching ARIA relationships.

Parameters

param prints a page parameter, falling back to the site parameter of the same name:

Current version: {{< param version >}}

Current version: v0.16.0

The shortcode fails the build when the named parameter does not exist. This is intentional: a missing release or repository value should not silently produce misleading documentation.

Existing rich content

OINK also ships local runtimes for inherited content features:

  • fenced mermaid, math, and markmap code blocks;
  • swaggerui and redoc API documentation shortcodes;
  • Docsy blocks, alerts, image, include, readfile, cards, and other established shortcodes.

See Shortcodes and Diagrams and formulae for the complete authoring reference.

Authoring rules

  • Prefer structured data over executable content.
  • Give images useful alt text and carousels a meaningful label.
  • Do not enable autoplay unless the content genuinely requires it.
  • Test several identical instances on one page when creating a new wrapper.
  • Verify keyboard navigation, focus visibility, dark and light themes, mobile layout, print output, and reduced-motion behavior.
  • Keep business-specific data components in the consuming site.

5 - Configuration

Configure OINK with native Hugo settings and focused theme parameters

OINK follows a “native first” configuration model. Site identity, languages, menus, outputs, taxonomies, markup, and modules stay in their Hugo-defined locations. Existing Docsy parameters remain where their semantics are useful. OINK adds only focused choices for behavior that cannot be inferred.

Configuration rules

  1. Prefer Hugo configuration over a theme-specific duplicate.
  2. Prefer an established Docsy parameter over an OINK synonym.
  3. Put brand, content, repository, and UI choices in their semantic locations.
  4. Keep internal vendor paths and template composition out of the public API.
  5. Fail early for invalid values or a missing required endpoint.

There is no oink.enabled flag and no params.oink.* tree. Adding either would create a second theme mode and make every fix, test, and document ambiguous.

A complete baseline

This example makes English primary and Simplified Chinese secondary:

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

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 }

outputs:
  home: [HTML]
  section: [HTML, RSS, print]

markup:
  goldmark:
    renderer:
      unsafe: true
    extensions:
      passthrough:
        enable: true
        delimiters:
          block: [['\[', '\]'], ['$$', '$$']]
          inline: [['\(', '\)']]
  highlight:
    noClasses: false

params:
  logo: icons/logo.svg
  offlineSearch: true
  offlineSearchIndex: summary
  offlineSearchMaxResults: 10
  github_repo: https://github.com/example/product-docs
  github_branch: main
  footer_icp: ''
  footer_icp_url: https://beian.miit.gov.cn/
  copyright:
    authors: Example Authors
    from_year: 2026
  ui:
    showLightDarkModeMenu: true
    quick_links: [docs, blog]
    sidebar_menu_foldable: true
    sidebar_item_overflow: wrap
    breadcrumb_disable: false

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

The module version is pinned in the site’s go.mod. A conventional theme checkout can instead use theme: oink with the repository under themes/oink/.

Languages

defaultContentLanguage determines the unprefixed primary site. Language weight controls the visible order. label is the language’s self-name, and locale supplies the full HTML and SEO locale. Add languageDirection: rtl to an RTL language.

File naming

For the colocated model used by this site:

content/docs/guide.md
content/docs/guide.zh.md

Files with the same base name are translations. Keep their logical page identity aligned. OINK reads Hugo’s translation relationships; it does not guess from arbitrary URL patterns.

Selector states

The selector needs no mode parameter. It is hidden for one configured language. With two or more, clicking the language icon advances to the next language by weight; hovering for half a second or focusing it opens the complete menu.

If the current page lacks a target translation, the target-language home page is used. Do not add dead page-shaped URLs merely to keep the selector on the same path.

Brand and repository

Set the site and per-language title and description. params.logo can point to a Hugo Asset or a path under static/. Keep favicons and social images in the documented asset locations.

Repository metadata drives “edit this page,” issue, and last-modified links:

params:
  github_repo: https://github.com/example/product-docs
  github_project_repo: https://github.com/example/product
  github_branch: main
  github_subdir: site

github_project_repo defaults to github_repo where supported. github_subdir is the content site’s path inside a monorepo. Keep github_branch resolvable; a display version is not necessarily a Git ref.

OINK retains Docsy menus and UI parameters and adds focused shell controls:

params:
  page_width: normal
  ui:
    quick_links: [docs, blog]
    sidebar_width_min: 220
    sidebar_width_max: 480
    sidebar_item_overflow: wrap
    sidebar_menu_compact: true
    sidebar_menu_foldable: true
    sidebar_root_enabled: true
    sidebar_root_menu: true
    sidebar_search_disable: false
    breadcrumb_disable: false
    showLightDarkModeMenu: true
    page_context_menu:
      enable: true
      links: []
    readingtime:
      enable: true

page_width accepts normal, wide, or full and can be overridden in page front matter. Sidebar minimum and maximum values are pixels used to clamp the desktop drag resizer. sidebar_item_overflow: wrap wraps long labels; other values retain the compact ellipsis behavior.

quick_links names top-level page references shown by the shell. Define their translated names in each language’s main menu.

The page context menu keeps Copy as Markdown, View Markdown, edit, issue, and print actions beside the page title at every viewport width. links is empty by default, so no external assistant receives page data unless the site opts in. Custom links accept URL-encoded {url}, {title}, and {markdown_url} placeholders:

params:
  ui:
    page_context_menu:
      enable: true
      links: []
      # - name: Ask an external assistant
      #   icon: fa-solid fa-wand-magic-sparkles
      #   url: https://assistant.example/new?source={markdown_url}&title={title}

Homepage content lives in data/home/<language>.yaml, with English used as the fallback. The supported top-level blocks are hero, metrics, capabilities, principles, cta, and footer. Each block is optional, so a site can keep a short landing page without copying the layout. For example:

hero:
  eyebrow: Local-first documentation
  title_lines:
    - words:
        - { mark: P, text: roduct, color: red }
        - { mark: D, text: ocs, color: blue }
  lead: Documentation built and served with Hugo.
  actions:
    - {
        label: Read the docs,
        url: docs/,
        icon: fa-solid fa-book,
        style: primary,
      }

footer:
  brand:
    name: Product Docs
    tagline: A short **Markdown-enabled** description.
    slogan: Clear answers, close to the product.
  columns:
    - title: Product
      links:
        - { label: Overview, url: docs/ }

The homepage renders the large brand-and-navigation footer above the common footline. The footline uses params.copyright on the left, optional params.footer_icp and params.footer_icp_url in the center, and every configured language on the right. Markdown in the copyright author and footer brand text is rendered as links and inline markup.

Local search is the starter default:

params:
  offlineSearch: true
  offlineSearchIndex: summary
  offlineSearchSummaryLength: 70
  offlineSearchMaxResults: 10

offlineSearchIndex controls how much text is downloadable in each language’s index. The scopes are cumulative: title indexes titles and taxonomy metadata; heading adds page headings; summary adds descriptions or summaries; and content also adds the complete body. content is the compatibility default, while summary is a smaller starting point for most documentation sites. offlineSearchMaxResults applies to both Lunr and the CJK substring fallback.

Each language receives a distinct index. Hosted alternatives remain supported through their established Docsy settings, but enabling them intentionally adds an external service boundary. Do not configure several competing search providers without also deciding which UI should be visible.

Content runtimes

Browser-only runtimes

Mermaid and KaTeX are detected from content. Enable Markmap at the site level:

params:
  markmap:
    enable: true
  mermaid:
    theme: default

Swagger UI, Redoc, Asciinema, ECharts, Infographic, and carousel assets load when their shortcodes appear. Their local runtime paths are internal and should not be configured.

Service endpoints

PlantUML and Diagrams.net require explicit endpoints:

params:
  plantuml:
    enable: true
    svg: true
    svg_image_url: https://diagrams.internal.example/plantuml/svg/
  drawio:
    enable: true
    drawio_server: https://diagrams.internal.example/

Leave the features disabled in an air-gap site unless those URLs are reachable inside the isolated network.

ECharts migration switch

Structured ECharts input is safe by default:

params:
  content:
    echarts_unsafe: false

Set it to true only while migrating reviewed legacy pages that contain JavaScript. Prefer unsafe=true on the smallest necessary shortcode instance, then rewrite the chart and remove the exception.

Page-level overrides

Hugo’s .Param lookup allows many site parameters to be overridden in front matter:

---
title: Wide reference
page_width: wide
hide_feedback: true
hide_readingtime: true
ui:
  no_left_sidebar: false
  scrollSpy:
    disable: false
---

Use overrides for real content differences, not to reconstruct a separate visual system page by page.

Avoid false configuration

Do not expose:

  • a switch between “Docsy” and “OINK” shells;
  • paths to vendored JavaScript, CSS, fonts, or internal partials;
  • duplicated language or repository values under a brand namespace;
  • toggles that merely select one of two copied implementations.

If a site needs a custom product matrix or portal, keep that component in the site and use a narrow hook or shortcode. A local business feature is clearer than a misleading global theme option.

Validate changes

After changing configuration:

  1. build with the minimum supported Hugo Extended version and the current validation version;
  2. test every configured language and one page without a translation;
  3. verify root and subpath baseURL output if both are supported;
  4. inspect local search and optional runtime requests;
  5. check the desktop and mobile shell, dark and light themes, and print output.

An accepted configuration is one that builds and behaves correctly, not merely one that parses as YAML.

6 - Deployment

Build OINK once and publish its static output

OINK deployment has two separate stages: Hugo builds a complete public/ directory, then a static host publishes that directory. Keep build verification and hosted verification distinct so a successful local command is not mistaken for a completed production release.

Production build

Run the pinned Hugo Extended version from the site root:

hugo --gc --minify --cleanDestinationDir

--gc removes unused cached resources, --minify produces production assets, and --cleanDestinationDir removes files left by an earlier build. Review the command before using the last option when publishDir points anywhere other than the site’s dedicated output directory.

The build must finish without warnings that hide missing content, endpoints, or resources. Inspect public/ locally before uploading it.

Local preview

For editing:

hugo server --disableFastRender

Hugo’s development server proves that the source can render. It is not a production host and its live-reload behavior is not part of the generated site. Always run a clean production build before release.

Static hosting

Any host that can serve directories and files can publish OINK:

  • object storage and a CDN;
  • GitHub Pages, GitLab Pages, or similar Git-driven static hosting;
  • Netlify, Cloudflare Pages, or another build-and-publish platform;
  • an Nginx, Caddy, Apache, or internal file server.

Set baseURL to the canonical production URL. If the site is published below a path such as https://example.com/manual/, include that path and test it; OINK’s local assets and component URLs are designed to preserve subpath deployments.

Cloudflare Pages

Connect Pages directly to the source branch. OINK does not require a GitHub Actions workflow that prebuilds and pushes an orphan Pages branch.

Use these settings for the current starter:

Setting Value
Production branch main, or the reviewed source branch
Root directory the standalone site directory
Build command hugo --gc --minify
Build output directory public
HUGO_VERSION 0.164.0
SKIP_DEPENDENCY_INSTALL 1

As of 2026-08-08, Cloudflare Pages’ v3 build image documents Hugo 0.147.7 as its default, below OINK’s minimum 0.160.1. Set HUGO_VERSION explicitly for both Production and Preview rather than relying on the moving platform default. SKIP_DEPENDENCY_INSTALL=1 prevents the platform’s generic dependency installer from adding a frontend installation step that the site does not need.

For previews that need the generated Pages URL as their canonical build URL:

hugo --gc --minify --baseURL "$CF_PAGES_URL"

Cloudflare documents public as Hugo’s standard output directory, the HUGO_VERSION override, and the CF_PAGES_URL base-URL pattern. Recheck the platform documentation when changing the build image or pinned Hugo version.

See the Cloudflare Hugo guide and Cloudflare build image reference.

Air-gap deployment

For a disconnected environment, transfer both the site source and a verified theme archive rather than depending on an initial Hugo Module download:

  1. verify the theme archive’s sidecar SHA-256 file;
  2. install a supported Hugo Extended binary inside the environment;
  3. extract the theme into the site’s themes/oink/ directory;
  4. set theme: oink and run hugo --gc --minify in the site;
  5. publish public/ to the internal static server.

Keep PlantUML and Diagrams.net disabled unless a reachable internal endpoint is configured. External links and embeds remain the content author’s responsibility.

Headers and caching

Fingerprint-bearing CSS and JavaScript can use long-lived immutable caching. HTML, search indexes, feeds, and sitemaps should use shorter caching or revalidation so a new deployment becomes visible promptly.

The project site includes a sample static/_headers file for hosts that recognize that convention. Treat it as a starting point, not a portable standard. Review security headers against the site’s actual inline content and integrations.

Preview and production URLs

Canonical, hreflang, Open Graph, feeds, and absolute links depend on baseURL. A production build should use the production URL; a preview may use its temporary URL when link validation or social metadata needs to be accurate.

Do not publish a preview build to production without rebuilding against the canonical URL. Conversely, do not reject a preview merely because it contains the intentionally supplied preview host.

Deployment acceptance

Verify each layer independently:

Source and configuration

  • the expected commit and pinned theme version are present;
  • baseURL, languages, menus, repository metadata, and optional endpoints are correct;
  • no unpublished draft or secret enters the public content tree.

Build artifact

  • a clean production build succeeds with the pinned Hugo Extended version;
  • English, Chinese, feeds, sitemaps, search indexes, and 404.html are present;
  • local assets resolve under both root and configured subpath;
  • the artifact contains required license and attribution surfaces.

Hosted site

  • the production URL returns the new artifact;
  • canonical and language-alternate URLs use the production origin;
  • navigation, search, language switching, dark mode, print, and representative components work in a real browser;
  • redirects, custom headers, cache policy, and 404 handling behave as configured;
  • an air-gap claim is backed by a browser network audit.

A green build log completes only the artifact stage. Deployment is complete after the hosted checks pass.

Rollback

Keep the previous known-good static artifact or hosting deployment identifier. If a new release fails hosted validation, restore that artifact first, then diagnose source or platform behavior. Rebuilding an old source commit with a new unpinned toolchain is not equivalent to restoring the original artifact.

7 - Migrating an existing site

Replace copied Docsy shells with OINK while preserving site-specific behavior

OINK is intended to replace copied common shells, runtimes, and shortcodes without forcing a bulk rewrite of ordinary content. A safe migration removes overrides by dependency, keeps product-specific behavior in the site, and validates a temporary copy before changing production.

Migration principles

  • Pin the target implementation; do not migrate production to an unversioned branch.
  • Inventory overrides before deleting them.
  • Remove common theme copies, not the site’s business logic.
  • Preserve content URLs, front matter, and shortcode behavior where the OINK API is compatible.
  • Make unsafe or online exceptions explicit and temporary.
  • Test build output, browser behavior, and hosted behavior as separate layers.

Pin the target

Pin a published tag in go.mod or use the complete versioned archive. During pre-release evaluation, a Hugo Module site can use an ignored Go workspace to resolve a local checkout without editing the committed module version:

go work init .
go work edit -replace=github.com/pgsty/oink=/absolute/path/to/oink
export HUGO_MODULE_WORKSPACE=go.work
hugo --gc --minify

The site’s hugo.yaml imports github.com/pgsty/oink; the workspace substitutes only the local checkout.

Inventory existing overrides

Group each site-level file into one of four classes:

Class Action
Exact or near-exact copy of common shell Remove after OINK validation
Reusable component now supplied by OINK Remove or mechanically rename
Narrow brand or product customization Keep, then reduce to the smallest hook
Business-specific data or interaction Keep in the site

Search layouts/, assets/, static/, configuration, and build workflows together. A copied shortcode often has a matching JavaScript bundle, stylesheet, vendor file, and CI install step.

Move configuration

Search and brand

Enable theme-owned local search and point the shell at the site’s own logo:

params:
  logo: img/product.svg
  offlineSearch: true

Continue using title, languages.*, github_repo, github_project_repo, github_branch, page_width, and ui.* in their existing semantic locations. Do not migrate values into an oink.* namespace.

ECharts legacy

Old Pigsty pages can contain JavaScript inside ECharts blocks. During a reviewed transition only:

params:
  content:
    echarts_unsafe: true

New and converted charts should use JSON or YAML. Remove the site-wide switch when migration is complete, or constrain unsafe=true to a shortcode that cannot yet be converted.

Fonts

The legacy Sass switch $td-enable-google-fonts: true now selects the local Open Sans files shipped by OINK; it no longer requests Google Fonts. $td-web-font-path is not part of the current build. A site that needs another font must provide approved local assets and their licenses.

Remove common overrides

After a temporary build proves equivalence, remove site copies of:

  • layouts/baseof.html and common docs/blog baseof*.html files;
  • common navbar, footer, sidebar, table-of-contents, search, head-CSS partials, and their hooks;
  • old common branded documentation-shell partials;
  • asciinema, echarts, infographic, doc-carousel, details, tab/tabpane, card, and param shortcode copies;
  • JavaScript, Lunr copies, carousel code, and SCSS used only by those removed implementations;
  • consumer PostCSS and Autoprefixer steps that no remaining site asset needs.

Delete by reference, not with a blanket removal of layouts/. Home pages, download pages, and portals may still call local partials such as icons, search dialogs, blog rows, or tag filters.

Keep site-specific behavior

Keep content and code whose semantics belong to the product:

  • product matrices and compatibility data;
  • pricing, downloads, portals, solutions, and catalog pages;
  • site-specific home-page composition;
  • custom redirects, headers, analytics, or identity integrations;
  • content components that encode business data rather than reusable presentation.

For the Pigsty family, pgvers, pgext_matrix, pgext_os_matrix, home-docs, and the current metric implementation remain at the site layer.

Reference-site matrix

The current migration plan uses these boundaries:

Site Remove or migrate Keep
SILO Common docs/blog shell, core shortcodes, duplicate runtimes; set logo: img/silo.svg Home page, download page, product data
PGSTY Common shell and core shortcodes; set logo: img/logo/logo.svg Portal, solution, and enterprise pages
SOW Common docs/blog shell, core shortcodes, duplicate runtimes; set logo: img/sow.svg Home page and repository-specific content
Pigsty Common shell, core shortcodes, duplicate runtimes; set logo: icons/logo.svg and temporarily review legacy ECharts Extension matrices, home/pricing pages, catalog styles

The matrix is a starting inventory, not permission to delete every similarly named file. Resolve actual template references in the target checkout.

Rehearsal workflow

Rehearse each migration in a disposable copy of the consuming site. Apply the local Oink workspace, remove one planned override group, block unintended network and frontend-tool access, and run the production build:

HUGO_MODULE_WORKSPACE=go.work hugo --gc --minify

Do not modify the source workspace during a rehearsal. Retain failed copies for diagnosis and record the exact theme commit, Hugo version, removed files, and output counts.

Current evidence

The latest recorded rehearsal on 2026-08-08 used Hugo Extended 0.164.0:

Site Rehearsal result HTML files
SILO Removed 20 common overrides; built complete English and Chinese content with OINK shell, same-origin search, and site logo 1,095
PGSTY Removed 20 common overrides; built the bilingual portal and a temporary docs page for shell validation 16
SOW Removed 20 common overrides; built complete English and Chinese content with OINK shell, same-origin search, and site logo 128
Pigsty Removed 24 common overrides; kept three business matrix shortcodes and enabled reviewed legacy ECharts unsafe mode 2,473

These are temporary-copy build results, not proof that the four production sites have been migrated or deployed.

Production rollout

For each site:

  1. create a dedicated migration branch;
  2. pin the OINK candidate and record its source commit;
  3. remove one coherent override group at a time;
  4. run a clean Hugo-only build and focused automated tests;
  5. compare representative home, docs, blog, special, and 404 pages;
  6. check mobile navigation, both color modes, language switching, search, print, and the site’s retained business components;
  7. deploy a preview and verify its real URLs and requests;
  8. merge and deploy only after review, then perform production smoke tests.

Record deliberate differences instead of forcing pixel equality where OINK intentionally changes the shell.

Rollback

Keep the pre-migration theme pin, site commit, and known-good deployment artifact. A rollback should restore all three consistently. Reintroducing a random subset of copied layouts against the new theme creates a hybrid state that is harder to diagnose than either complete version.

8 - Release process

Release the Oink theme and update its independent project site

Oink treats implementation, validation, publication, and deployment as different states. A green local build is useful evidence, but it is not a public tag, a downloadable module, or a deployed documentation update.

Release states

State Required evidence
Source complete Scope, docs, changelog, attribution, and review are complete
Validated Theme-module and project-site checks pass
Published An immutable root tag exists in pgsty/oink and resolves through Go
Documented pgsty/oink.pgsty.com pins and documents that tag
Deployed The hosted documentation and target consumers pass verification

Report the exact state and evidence; do not call a local build a release.

Versioning

Theme releases use root tags such as vX.Y.Z in github.com/pgsty/oink. The theme is now the repository’s root module, so nested theme/vX.Y.Z tags are no longer used.

The project site’s version parameter identifies a published site variant and is not automatically a Git ref. Installation instructions and go.mod must use the actual resolvable theme tag.

Validate the theme repository

From a clean pgsty/oink checkout:

  1. inspect the source diff and attribution changes;
  2. verify every VENDOR.json file and SHA-256 entry;
  3. confirm the repository has no generated public/, resource cache, node_modules/, or embedded example site;
  4. build a minimal consumer through the Hugo Module path with the minimum and current supported Hugo Extended versions;
  5. inspect the module zip and confirm layouts, assets, translations, static files, licenses, and notices are present.

The module zip test matters because Go excludes special directory names such as vendor from published modules. Oink stores bundled dependencies under assets/third_party/ so they survive module distribution.

Validate the project site

Clone pgsty/oink and pgsty/oink.pgsty.com as siblings, then connect them with an ignored workspace:

cd oink.pgsty.com
go work init .
go work edit -replace=github.com/pgsty/oink=../oink
export HUGO_MODULE_WORKSPACE=go.work
npm install
npm test

Inspect representative English and Chinese pages, mobile navigation, both color modes, local search, print output, diagrams, API documentation, and 404 pages. This validates the candidate against the site; it does not publish either repository.

Tag and publish the theme

After review, create one immutable signed root tag in the theme repository:

git tag -s vX.Y.Z -m "Oink vX.Y.Z"
git push origin main vX.Y.Z

Pushing and creating a GitHub release require explicit authorization. After the tag is public, verify it from a clean environment:

hugo mod get github.com/pgsty/[email protected]
hugo mod graph

If an offline archive is attached to the release, publish and independently verify its SHA-256 checksum. Keep LICENSE, NOTICE, and VENDOR.json in the archive.

Update the project site

Once the theme tag resolves publicly, update the independent site repository:

hugo mod get github.com/pgsty/[email protected]
hugo mod tidy
npm test

Commit go.mod, go.sum, version parameters, changelog, and upgrade guidance together. Deploy previews first, then advance the production publishing branch only after review.

Post-release verification

After publication:

  1. fetch the tag from a clean clone and inspect its signature;
  2. resolve the module through the public Go proxy;
  3. build a minimal new site with the documented commands;
  4. open the production documentation and verify module instructions, canonical links, languages, search, and assets;
  5. verify any released archive and checksum;
  6. record the final tag, module version, hosted URLs, and artifact hashes.

Hotfix and rollback

A hotfix follows the same evidence chain with a smaller scope. Never move or replace a published tag. Roll back a site deployment to a known artifact, then publish a new patch version of the theme when necessary.

Definition of done

A release is complete only when the approved tag exists, the public module resolves, required checks pass, the project site pins the tag, and hosted smoke tests succeed. Anything less should be reported by its actual state.