Understand the build and runtime boundaries.
This is the multi-page printable view of this section. Click here to print.
OINK
- 1: Getting started
- 2: Architecture
- 3: Local-first operation
- 4: Content components
- 5: Configuration
- 6: Deployment
- 7: Migrating an existing site
- 8: Release process
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.jsonwith 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
- Install the Hugo Module.
- Read the architecture and local-first model.
- Review components and configuration.
- Choose a deployment and follow the release checklist.
- For an existing Docsy site, start with the migration guide.
1 - Getting started
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:
- build from a clean checkout with the committed module files;
- run
hugo --gc --minifywith the pinned Hugo Extended version; - browse representative English and Chinese pages;
- verify language switching, search, mobile navigation, dark mode, and print;
- 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
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:
- page bundles and Markdown content from the consuming site;
- native Hugo configuration and supported theme parameters;
- theme templates, translations, SCSS, and JavaScript;
- 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
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.
Local search
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:
- start with the verified theme archive and an empty Hugo cache;
- block outbound HTTP, HTTPS, and Go module proxies;
- run the production Hugo command;
- browse English and Chinese pages from the generated output;
- exercise search, dark mode, diagrams, API docs, and content components;
- 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
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.
Cards and carousel
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?
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 >}}
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, andmarkmapcode blocks; swaggeruiandredocAPI 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
alttext and carousels a meaningfullabel. - 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
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
- Prefer Hugo configuration over a theme-specific duplicate.
- Prefer an established Docsy parameter over an OINK synonym.
- Put brand, content, repository, and UI choices in their semantic locations.
- Keep internal vendor paths and template composition out of the public API.
- 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.
Navigation and layout
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 and footer
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.
Search
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:
- build with the minimum supported Hugo Extended version and the current validation version;
- test every configured language and one page without a translation;
- verify root and subpath
baseURLoutput if both are supported; - inspect local search and optional runtime requests;
- 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
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:
- verify the theme archive’s sidecar SHA-256 file;
- install a supported Hugo Extended binary inside the environment;
- extract the theme into the site’s
themes/oink/directory; - set
theme: oinkand runhugo --gc --minifyin the site; - 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.htmlare 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
404handling 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
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.htmland common docs/blogbaseof*.htmlfiles;- 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, andparamshortcode 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:
- create a dedicated migration branch;
- pin the OINK candidate and record its source commit;
- remove one coherent override group at a time;
- run a clean Hugo-only build and focused automated tests;
- compare representative home, docs, blog, special, and
404pages; - check mobile navigation, both color modes, language switching, search, print, and the site’s retained business components;
- deploy a preview and verify its real URLs and requests;
- 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
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:
- inspect the source diff and attribution changes;
- verify every
VENDOR.jsonfile and SHA-256 entry; - confirm the repository has no generated
public/, resource cache,node_modules/, or embedded example site; - build a minimal consumer through the Hugo Module path with the minimum and current supported Hugo Extended versions;
- 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:
- fetch the tag from a clean clone and inspect its signature;
- resolve the module through the public Go proxy;
- build a minimal new site with the documented commands;
- open the production documentation and verify module instructions, canonical links, languages, search, and assets;
- verify any released archive and checksum;
- 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.