Understand the build and runtime boundaries.
This is the multi-page printable view of this section. Click here to print.
Welcome to OINK
- 1: Get started
- 1.1: Use the Oink theme
- 1.1.1: Before you begin
- 1.1.2: Inspect the bilingual project site
- 1.1.3: Create a new site: start from scratch
- 1.2: Other setup options
- 1.3: Run OINK in a container
- 1.4: Basic site configuration
- 1.5: Troubleshooting and known issues
- 2: Content and Customization
- 2.1: Adding Content
- 2.2: AI-agent support
- 2.3: Analytics, user feedback, and SEO
- 2.4: Diagrams and formulae
- 2.5: Logos and Images
- 2.6: Look and Feel
- 2.7: Navigation and Menus
- 2.8: Print Support
- 2.9: Repository links and page information
- 2.10: Search
- 2.11: Shortcodes
- 2.12: Taxonomy Support
- 2.13: Versioning
- 3: OINK
- 3.1: Getting started
- 3.2: Architecture
- 3.3: Local-first operation
- 3.4: Content components
- 3.5: Configuration
- 3.6: Deployment
- 3.7: Migrating an existing site
- 3.8: Release process
- 4: Deployment and previews
- 4.1: Deployment with Amazon S3 and CloudFront
- 4.2: Deployment on GitHub Pages
- 4.3: Serving your site locally
- 4.4: Deployment on Netlify
- 4.5: Page chrome
- 5: Multi-language support
- 6: Update OINK
- 6.1: Update the OINK Hugo module
- 6.2: Migrate from the Docsy npm package
- 6.3: Update an OINK Git submodule or clone
- 6.4: Migrate a Docsy site to OINK
- 7: Best Practices
- 7.1: Hugo Content Tips
- 7.2: Organizing Your Content
- 8: Contribution guidelines
- 9: About OINK
v0.16.0
Welcome to the OINK user guide for version v0.16.0. This guide
covers the theme’s Hugo-only build, local-first runtime, multilingual framework,
content components, customization, and deployment.
What is OINK?
OINK is an independent theme for the Hugo static site generator, designed for medium and large technical documentation sets. It evolves Docsy directly: Docsy’s mature content model and documentation features remain available, while OINK provides a new canonical shell, local dependencies, and reusable components drawn from production PGSTY sites.
A consuming site can build with Hugo Extended alone. It does not need Node.js, npm, PostCSS, Autoprefixer, or a CDN. Bootstrap, Font Awesome, fonts, local search, diagrams, API documentation runtimes, and content components ship with the theme and are loaded only when a page needs them.
OINK includes:
- a responsive documentation and blog shell with navigation, table of contents, search, print output, dark mode, and accessible interactions;
- a general multilingual framework with translated-page routing, missing-page fallback, language weights, RTL support, and SEO alternate metadata;
- local Mermaid, KaTeX, Markmap, Swagger UI, Redoc, Asciinema, ECharts, and Infographic runtimes;
- reusable details, tabs, cards, navigation cards, and document carousels;
- a bilingual starter, Cloudflare Pages guidance, an air-gap distribution, and an auditable vendor manifest.
OINK itself does not provide source hosting or deploy your generated site. Keep your project in GitHub, GitLab, a private Git service, or a local repository, then publish Hugo’s static output with the platform of your choice.
Is OINK for me?
OINK is most useful when a documentation project has many pages, several content types, multiple languages, or strict reproducibility and network isolation requirements. It is also a good fit when several sites should share a single maintained shell instead of copying layouts, scripts, and shortcodes.
For a project with only one or two pages and no structured navigation, a README or a smaller Hugo theme may be simpler. For a heavily application-driven portal, use OINK for the documentation surface and keep business-specific components in the site rather than forcing them into the theme.
Ready to get started?
Read the OINK overview to understand the product boundaries, then build the bilingual starter. The remaining user guide documents the inherited Docsy content model and the compatible APIs that OINK continues to support.
1 - Get started
Oink is a Hugo theme whose complete browser runtime ships with the theme. A consumer site builds with Hugo Extended alone: no Node.js package installation, PostCSS step, CDN, or build-time remote asset download is part of the default path.
Choose a starting point
- Hugo Module — recommended: import
github.com/pgsty/oinkin an existing or new Hugo site. See the Oink quick start. - Project site: use the independent
pgsty/oink.pgsty.comrepository as a complete bilingual configuration and regression reference. - Existing Docsy site: follow the migration guide to remove common overrides and the consumer npm asset pipeline without rewriting content.
Install the prerequisites
Install Git, Go, and Hugo Extended 0.160.1 or newer. See
Before you begin for platform
notes and verification commands.
Add Oink
From the site root:
hugo mod init github.com/example/product-docs
hugo mod get github.com/pgsty/oink@THEME_REF
Then import the theme in hugo.yaml:
module:
imports:
- path: github.com/pgsty/oink
Pin THEME_REF to a released tag or immutable commit and commit go.mod and
go.sum.
Build contract
The same commands preview and build every supported module consumer:
hugo server --disableFastRender
hugo --gc --minify
Next steps
- Set the basic configuration.
- Add repository, copyright, logo, and menu values.
- Put translations side by side as
page.mdandpage.zh.md. - Add and customize content.
- Choose a deployment target.
1.1 - Use the Oink theme
Oink separates the consuming site from the maintained theme. A site owns its content, brand assets, configuration, and business components; the theme owns the common shell, styles, browser runtimes, and reusable shortcodes.
Recommended setup
Import github.com/pgsty/oink as a pinned Hugo Module. The independent
pgsty/oink.pgsty.com repository
demonstrates the full production contract with English and Chinese content,
local search, dark mode, diagrams, API documentation, and component examples.
Experienced Hugo users can start from scratch. Existing Docsy sites should use the migration guide instead of recreating the shell by hand.
Theme source options
The preferred source is a released github.com/pgsty/oink module tag. A
complete release archive, pinned Git submodule, or pinned clone also works. Read
Other setup options for the trade-offs;
production must never follow an unversioned branch.
Build contract
Whichever source option is selected, this command must build the site:
hugo --gc --minify
Node-based commands in the project-site repository are maintainers’ regression tooling, not prerequisites for a consuming site.
1.1.1 - Before you begin
The consumer prerequisite is Hugo Extended. Git and Go are conditional on how the theme source is obtained.
Install Hugo Extended
Install version 0.160.1 or newer. The current validation
baseline is 0.164.0. A release’s support matrix takes precedence when these
values change.
Verify the selected binary:
hugo version
The output must contain extended. Standard Hugo cannot compile the theme’s
SCSS. Use Hugo’s official installation guides for the platform and pin the
same version in local development and CI.
Install Git when needed
Git is required to clone the site, use submodules, preserve .GitInfo, or fetch
a theme checkout. Verify it with:
git --version
A site built from an already extracted offline archive can run Hugo without network access, but keeping the source in version control remains recommended.
Install Go only for Hugo Modules
Hugo’s module commands use Go. Install Go when the site imports the theme as a Hugo Module, then verify:
go version
hugo mod graph
A versioned archive, adjacent theme directory, or Git submodule does not require Go at site build time.
Do not install a frontend toolchain
OINK ships Bootstrap, Font Awesome, LTR and RTL CSS, fonts, search, and browser runtimes as local versioned assets. Consumer sites do not install Node.js, npm, PostCSS, Autoprefixer, or RTLCSS for the theme.
Node-based commands in the project-site repository are maintainer-only tools. The production consumer command is:
hugo --gc --minify
Check the complete distribution
For offline or air-gapped use, confirm that the theme archive contains go.mod,
hugo.yaml, assets/, layouts/, static/, i18n/, LICENSE, NOTICE, and
VENDOR.json. Install Hugo Extended before entering the isolated environment,
then run the same build command with network access disabled.
What’s next?
1.1.2 - Inspect the bilingual project site
The independent
pgsty/oink.pgsty.com repository is
the complete bilingual example and regression site. It is intentionally more
comprehensive than a starter: use it as a reference, then keep only the content
and configuration your product needs.
Clone the project site
After the Oink theme has a public release, clone and build the site directly:
git clone https://github.com/pgsty/oink.pgsty.com.git product-docs
cd product-docs
hugo --gc --minify
The committed go.mod pins github.com/pgsty/oink. For local theme
development, clone the theme as a sibling and use the workspace commands
documented in the
Oink quick start.
Run the site checks
Hugo alone builds the site. Node.js is used only for the project site’s formatting, link, translation, and regression checks:
npm install
npm test
Open the generated site and check both English and Chinese pages. Use the language switcher from a translated detail page, not only from the home page.
Replace the example identity
Edit hugo.yaml and the files under config/, then replace:
- site and per-language titles and descriptions;
baseURL;- repository and branch URLs;
- copyright holder and starting year;
- logo and brand assets;
- English and Chinese menu labels.
Do not create an oink.* parameter namespace. Use Hugo’s language, menu,
module, output, and markup settings plus the documented theme parameters.
Replace the example content
Keep each translation pair together:
content/docs/getting-started.md
content/docs/getting-started.zh.md
Delete historical and regression content that the product does not need. Remove an example asset only after no page references it.
For translated headings, use the English rendered ID explicitly:
## Configure search
## 配置搜索 {#configure-search}
Put the new site in version control
Change the module path, repository metadata, and remote before publishing a
derived site. Keep the Oink version pinned in go.mod. Do not commit generated
public/ output unless the hosting workflow explicitly requires it.
What’s next?
- Review basic configuration.
- Learn the content components.
- Configure deployment.
- Use the release checklist.
1.1.3 - Create a new site: start from scratch
The independent bilingual project site is a useful reference. Use this procedure to create a smaller site with its own content structure.
Create the site skeleton
Run:
hugo new site --format yaml my-new-site
cd my-new-site
Initialize the site module and pin Oink:
hugo mod init github.com/example/my-new-site
hugo mod get github.com/pgsty/oink@THEME_REF
Add minimum configuration
Use this as hugo.yaml:
title: Product Docs
baseURL: https://docs.example.com/
defaultContentLanguage: en
languages:
en:
label: English
locale: en-US
weight: 1
menus:
main:
- { name: Docs, pageRef: /docs, weight: 10 }
- { name: Blog, pageRef: /blog, weight: 20 }
zh:
label: 简体中文
locale: zh-CN
weight: 2
menus:
main:
- { name: 文档, pageRef: /docs, weight: 10 }
- { name: 博客, pageRef: /blog, weight: 20 }
markup:
goldmark:
renderer:
unsafe: true
highlight:
noClasses: false
params:
offlineSearch: true
ui:
showLightDarkModeMenu: true
sidebar_menu_foldable: true
module:
imports:
- path: github.com/pgsty/oink
hugoVersion:
extended: true
min: 0.160.1
Commit go.mod and go.sum. Do not add npm mounts or a PostCSS pipeline.
Add bilingual content
Create these files:
content/
├── _index.md
├── _index.zh.md
├── docs/
│ ├── _index.md
│ ├── _index.zh.md
│ ├── getting-started.md
│ └── getting-started.zh.md
└── blog/
├── _index.md
└── _index.zh.md
Every page needs front matter. For example, content/docs/getting-started.md:
---
title: Getting started
weight: 10
---
## Install {#install}
Install the product.
Its getting-started.zh.md translation keeps the explicit heading ID:
---
title: 开始使用
weight: 10
---
## 安装 {#install}
安装产品。
Using the same explicit ID in both examples is harmless and makes the intended cross-language contract visible. In a translated existing page, copy the ID from the English rendered HTML.
Preview and build
Run the development server:
hugo server --disableFastRender
Then verify the production build separately:
hugo --gc --minify
Check /docs/, /zh/docs/, the language selector, local search indexes, and
the browser console before adding custom layouts.
Add features incrementally
Copy logo and brand assets first, then add repository links and menus. Add diagrams, API documentation, and content components only on pages that need them; OINK will publish their local runtimes on demand.
If a site needs a business-specific shortcode, keep it under the site’s own
layouts/_shortcodes/. Move it into the theme only after its interface is free
of site assumptions and multiple sites can reuse it.
What’s next?
- Expand the basic configuration.
- Learn how to add content.
- Review the OINK architecture.
- Select a deployment target.
1.2 - Other setup options
The recommended installation uses the github.com/pgsty/oink Hugo Module. The
following options change how Hugo obtains the same theme source; they do not
change content or the Hugo-only build command.
Prerequisites
Every option requires Hugo Extended 0.160.1 or newer. Git
options require Git, and Hugo Modules require Go. None of the options requires
Node.js, npm, PostCSS, or Autoprefixer for the consuming site.
Option 1: complete release archive
The complete offline archive contains the theme, local browser runtimes, fonts, licenses, notices, vendor manifest, and checksums. It is the preferred input for air-gapped builds and the simplest way to preserve an exact distribution.
Extract the theme under the site’s themes/ directory:
site/
├── hugo.yaml
└── themes/
└── oink/
Configure:
theme: oink
Verify the archive checksum before extracting it. Use only an archive attached to an explicit release, not a locally assembled file presented as a published distribution.
Option 2: Git submodule
A submodule records the exact OINK repository commit in the site repository:
git submodule add https://github.com/pgsty/oink.git themes/oink
git -C themes/oink fetch --tags
git -C themes/oink checkout THEME_REF
git add .gitmodules themes/oink
git commit -m "Add OINK theme at THEME_REF"
Configure the nested theme path:
theme: oink
CI must initialize submodules before running Hugo. Pin THEME_REF to a release
tag or immutable commit; do not leave production on main.
Option 3: pinned Git clone
A clone works when the hosting platform requires the complete theme tree in the build input or when the site vendors a reviewed copy:
git clone https://github.com/pgsty/oink.git themes/oink
git -C themes/oink checkout THEME_REF
Use the same theme: oink setting. Record the resolved commit and the process
that restores the clone. If the files are committed into the site repository,
preserve OINK’s LICENSE, NOTICE, and VENDOR.json.
OINK is not distributed as an npm package. Existing Docsy npm consumers should follow the npm migration guide.
Option 4: Hugo Module
Pin the public module to a release tag or immutable commit:
hugo mod get github.com/pgsty/oink@THEME_REF
hugo mod tidy
Import it in hugo.yaml:
module:
imports:
- path: github.com/pgsty/oink
For local theme development, use an ignored Go workspace that includes the site module and a sibling OINK checkout.
Preview and verify
All source options use the same commands:
hugo server --disableFastRender
hugo --gc --minify
Verify that a clean production build succeeds with no node_modules directory,
that local assets resolve under the configured baseURL, and that both English
and Chinese pages and search indexes are present.
See Update OINK for version changes and override review.
1.3 - Run OINK in a container
A container is optional: OINK itself only needs Hugo Extended. Use a container when the team wants a pinned tool image or does not install Hugo on developer workstations.
Create the Hugo image
The following Dockerfile installs the currently validated Hugo Extended
version from its release package. Keep the version aligned with the theme’s
support matrix.
FROM debian:bookworm-slim
ARG HUGO_VERSION=0.164.0
ARG TARGETARCH
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl git \
&& curl -L -o /tmp/hugo.deb \
"https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-${TARGETARCH}.deb" \
&& apt-get install -y /tmp/hugo.deb \
&& rm -rf /var/lib/apt/lists/* /tmp/hugo.deb
WORKDIR /src
EXPOSE 1313
ENTRYPOINT ["hugo"]
CMD ["server", "--bind", "0.0.0.0", "--disableFastRender"]
Build it from the site root:
docker build -t oink-hugo .
The image build downloads Hugo. For an air-gapped environment, mirror the base image and Hugo package in advance or use OINK’s complete offline distribution with an approved internal image.
Preview the site
Mount the complete site source, including its adjacent or vendored theme:
docker run --rm -it \
-p 1313:1313 \
-v "$PWD:/src" \
oink-hugo
Open http://localhost:1313/. Changes on the host are visible to Hugo’s live reload process inside the container.
Run a production build
Override the default server command:
docker run --rm \
-v "$PWD:/src" \
oink-hugo --gc --minify
The generated site is written to public/ in the mounted source directory.
Ensure the container user can write there; in a shared environment, run with a
mapped user ID or fix ownership according to local policy.
No Node.js, npm, PostCSS, or remote browser asset step belongs in this image.
1.4 - Basic site configuration
Hugo reads site-wide settings from hugo.yaml, hugo.toml, or hugo.json. The
The Oink project site uses YAML because multilingual menus and theme options
remain easy to scan and review.
Minimum configuration
The following excerpt shows the important structure for the Hugo Module.
title: Product Documentation
baseURL: https://docs.example.com/
defaultContentLanguage: en
languages:
en:
label: English
locale: en-US
weight: 1
title: Product Documentation
menus:
main:
- name: Docs
pageRef: /docs
weight: 10
- name: Blog
pageRef: /blog
weight: 20
zh:
label: 简体中文
locale: zh-CN
weight: 2
title: 产品文档
menus:
main:
- name: 文档
pageRef: /docs
weight: 10
- name: 博客
pageRef: /blog
weight: 20
markup:
goldmark:
renderer:
unsafe: true
highlight:
noClasses: false
params:
offlineSearch: true
github_repo: https://github.com/example/product-docs
github_branch: main
copyright:
authors: Example Authors
from_year: 2026
ui:
showLightDarkModeMenu: true
sidebar_menu_foldable: true
breadcrumb_disable: false
module:
imports:
- path: github.com/pgsty/oink
hugoVersion:
extended: true
min: 0.160.1
English has weight 1 and is the default language; Simplified Chinese has weight 2; additional languages follow. The language selector uses this order when a click cycles to the next language and when the full hover menu is rendered.
Content translations
Put translations beside each other:
content/
├── _index.md
├── _index.zh.md
├── docs/
│ ├── _index.md
│ ├── _index.zh.md
│ ├── install.md
│ └── install.zh.md
└── blog/
├── release.md
└── release.zh.md
Keep route-affecting metadata aligned. Translate titles, descriptions, menu labels, summaries, tags, image alternatives, and visible shortcode strings. Use the English rendered heading ID as an explicit ID on each translated heading so that fragments remain stable across languages.
Local search and browser resources
offlineSearch: true enables the theme’s same-origin Lunr index and CJK
fallback. The index is generated per language. Do not configure a public search
service unless the site intentionally accepts that network dependency.
Mermaid, KaTeX, Markmap, Swagger UI, Redoc, Asciinema, ECharts, and Infographic are provided locally and loaded per page. PlantUML and Draw.io are service-based exceptions: configure an approved endpoint explicitly or keep them disabled.
Branding and repository links
Set title, per-language titles, params.logo, repository URLs, copyright, and
menus at the site layer. OINK does not add an oink.* configuration tree; it
uses Hugo and compatible Docsy parameter locations.
Repository metadata enables edit, view, issue, and age information on content
pages. Keep github_repo, github_project_repo, github_branch, and
github_subdir consistent with the source layout.
Production defaults
- Use a real production
baseURL, including any subpath. - Keep online analytics, comments, Google CSE, Algolia, and remote embeds off unless they are an explicit product choice.
- Pin Hugo Extended and the theme release in CI.
- Run
hugo --gc --minifyas the production command. - Keep
LICENSE,NOTICE, and the vendor manifest in redistributed archives.
See the project site’s complete hugo.yaml for a buildable reference.
1.5 - Troubleshooting and known issues
Start diagnosis from a clean production build:
hugo --gc --minify --logLevel info
The consumer command should not invoke npm, PostCSS, Autoprefixer, or download theme browser assets.
Build issues
Hugo is not Extended or is too old
Run hugo version. The output must include extended, and the version must be
at least 0.160.1. If a shell, editor, CI runner, or
container still selects an older binary, inspect its PATH and pinned tool
configuration rather than installing another copy blindly.
The theme cannot be found
An error such as module "github.com/pgsty/oink" not found means that Hugo
cannot resolve the configured theme. Check the selected installation mode:
- for a Git checkout, the
themename and directory path must agree; - for a Hugo module, run
hugo mod graphand inspectgo.mod,go.sum, and any configured Hugo workspace or replacement; - for a CI checkout, initialize the pinned submodule or restore the complete release archive before running Hugo.
A local browser asset is missing
Do not fix a missing Bootstrap, Font Awesome, Lunr, Mermaid, or other OINK asset
by adding a CDN URL. Confirm that the distribution is complete and contains
assets/third_party/, assets/js/third_party/, static/webfonts/, and
VENDOR.json. Re-extract or re-fetch the same pinned release if files are
missing.
Language and link issues
A translated page does not appear
Check all four conditions:
languages.zhexists and has a weight inhugo.yaml.- The file is named
page.zh.md, including lowercasezh. - The translated front matter does not set
draft: trueor a future date. - Route-affecting metadata matches the source unless a different route is intentional.
The language selector links to a page translation when Hugo reports one; otherwise it deliberately falls back to the target-language home page.
A fragment link opens the page but not the heading
Translated heading text normally generates a different automatic ID. Add the English rendered ID explicitly to the translated heading:
## 安装 {#installation}
Do not infer IDs for headings containing shortcodes or inline HTML. Inspect the English rendered HTML, then compare the English and Chinese heading ID lists.
Search issues
With offlineSearch: true, each language produces its own search index. Check
that offline-search-index.en.json and offline-search-index.zh.json exist in
the output and that the browser requests them from the site’s base URL. A wrong
baseURL is a common cause of missing indexes on subpath deployments.
Chinese tokenization uses the theme’s CJK fallback. If results are empty, first verify that the Chinese page content is present in the Chinese index rather than changing the tokenizer.
Platform issues
macOS reports too many open files
Large live-preview trees can exceed the shell’s open-file limit. Inspect the
current limit with ulimit -n and raise it temporarily for the current shell if
local policy permits. Prefer excluding generated or unrelated directories from
the watched tree before applying a machine-wide limit change.
Windows Subsystem for Linux is slow or misses changes
Run Hugo against a Linux filesystem path rather than a Windows-mounted path. Cross-filesystem notification and permission behavior can make live reload slow or unreliable.
Diagnostic checklist
- Reproduce with the exact pinned Hugo Extended version.
- Remove stale
public/andresources/output through the project’s normal clean command, then rebuild. - Compare development and production configuration layers.
- Check the first build error, not only the final cascading message.
- Test a minimal page to separate theme behavior from site overrides.
- Re-enable site overrides and content components in small groups.
- Inspect the browser console and network log for the failing page.
2 - Content and Customization
2.1 - Adding Content
OINK uses Hugo’s content model: Markdown carries the information, front matter carries page metadata, and layouts turn both into a static site. This guide describes the conventions used by the bundled English and Simplified Chinese sample site.
Content root directory
Site content lives below content/. A multilingual site can use separate roots
such as content/en/ and content/zh/, or translated filename suffixes in one
mounted tree. This repository uses the second form:
content/docs/content/
├── adding-content.md
└── adding-content.zh.md
The English file is the source page and the .zh.md file is its Simplified
Chinese translation. Both files share the same logical path after Hugo applies
the language suffix.
Keep generated files and files that must be copied byte-for-byte outside the
content tree. Put those in static/ as described in
Adding static content.
Content sections and templates
Every top-level content directory is a Hugo section. OINK includes layouts for:
docs: documentation with a section tree, table of contents, breadcrumbs, previous/next navigation, and repository links;blog: dated articles, taxonomy metadata, feeds, and chronological lists;community: project and contributor links;- default pages: landing pages without the documentation sidebar.
Hugo chooses a layout from the content section. A page below content/docs/
therefore uses the docs layout. Set type in front matter only when a page
must use another section’s layout.
Custom sections
Create a directory below the content root, then give its pages a type when the default layout is not sufficient:
---
title: Architecture decisions
description: Accepted design decisions for the project.
type: docs
weight: 30
---
For section-wide behavior, put shared values in the section’s _index.md
cascade rather than repeating them on every page. Add a project layout under
layouts/ only when no existing OINK layout or partial is suitable.
Doc-rooted sites
EXPERIMENTAL
A documentation-first site can publish the docs section at the URL root while
keeping source files under content/.../docs/:
permalinks:
page:
docs: /:sections[1:]/:slug/
section:
docs: /:sections[1:]
The docs section landing page then becomes the home page. Add this front matter to the physical site-root index for each language so it can still act as a link without competing for the same output path:
build: { render: link }
Check for path conflicts
Docs now share the URL root with blog, community, and other sections. Build with
--printPathWarnings and resolve every duplicate target before publishing:
hugo --printPathWarnings
Legacy docs-only setup
Older Docsy examples used a front matter cascade to force page types. Remove that workaround when moving to the permalink-based doc-rooted setup; otherwise the home page and section layouts can resolve inconsistently.
Page front matter
Front matter is page metadata written in YAML, TOML, or JSON. OINK’s sample site uses YAML:
---
title: Local-first architecture
linkTitle: Local-first
description: How OINK removes browser and build-time CDN dependencies.
weight: 20
date: 2026-08-08
tags: [architecture, offline]
---
title is the practical minimum. In maintained documentation, also provide a
concise description for search and metadata, and a weight when order
matters. Use linkTitle only when navigation needs a shorter label.
Translations should localize human-facing metadata while preserving structural values:
---
title: 本地优先架构
linkTitle: 本地优先
description: OINK 如何消除浏览器端与构建期的 CDN 依赖。
weight: 20
date: 2026-08-08
tags: [架构, 离线]
---
Do not translate keys, shortcode names, configuration keys, file paths, or stable identifiers.
Footer metadata
Docs and blog pages render a compact metadata block above the site footer. The
last-modified date comes from Hugo’s .Lastmod value. Two optional front matter
fields add provenance notices:
lastmod: 2026-08-09
upstream_attribution: https://upstream.example/docs/page/
downstream_modified: true
upstream_attribution links to the upstream source and its attribution.
downstream_modified: true states that the downstream project changed the page.
Omit either field when its notice does not apply.
Page content
Write pages in Markdown unless a layout genuinely requires HTML. Hugo renders Markdown with Goldmark and supports attributes, footnotes, tables, task lists, render hooks, and fenced code blocks.
Markdown
Keep source readable without the rendered site:
- use ATX headings (
## Heading); - put blank lines around lists, blocks, and fenced code;
- specify the language of every code fence when one exists;
- use descriptive link text and image alternative text;
- wrap prose at a review-friendly width, but never reflow code or URLs.
OINK adds render hooks for blockquote alerts and for Mermaid, math, chemistry, Markmap, and PlantUML code blocks. See Diagrams and Formulae.
Markup, shortcodes, and content features
Use standard Markdown for ordinary prose. Use a shortcode when it supplies meaningful behavior such as tabs, cards, a terminal recording, an API viewer, or a safe chart. Shortcodes are part of the content contract: verify their arguments in both languages and avoid copying rendered HTML into translations.
Alerts
OINK supports GitHub-style blockquote alerts and optional Obsidian-style titles:
> [!TIP]
>
> Run the translation audit before every release.
> [!WARNING] Stable anchors required
>
> A translated heading must keep the English page's rendered ID.
Supported semantic types include NOTE, TIP, IMPORTANT, WARNING, and
CAUTION, plus the Bootstrap-compatible types and NB. Use alerts sparingly:
important instructions must still make sense to screen readers and in print. See
Alerts for appearance.
Links
Use root-relative links for stable public routes and ordinary relative links for
nearby pages or bundle resources. Hugo’s ref and relref shortcodes validate
content references and account for language and permalink rules:
[Configuration]({{< ref "/docs/oink/configuration" >}})
For bilingual pages:
- link to the logical page, not directly to a
.zh.mdfilename; - keep fragment IDs language-neutral;
- verify that both language variants resolve the same fragment;
- use
relrefwhen the destination must remain relative to the current host.
Run the internal-link check after changing routes or headings.
Content style
Write task-oriented documentation in direct language. Introduce a concept before
its configuration, state defaults explicitly, and distinguish local build
verification from deployment or publication. The Chinese edition follows the
terminology and typography rules in oink.pgsty.com/TRANSLATION.md.
Page bundles
A standalone page is a single Markdown file. A leaf bundle is a directory with
an index.md and page resources:
content/docs/tutorial/
├── index.md
├── index.zh.md
├── architecture.svg
└── example.yaml
Both language pages can use the same image and downloadable file. Hugo normally shares page resources across language variants on a single host, so do not duplicate identical binary assets. Localize an image only when it contains meaningful text; give the localized resource a clear language suffix.
Use branch bundles (_index.md) for sections that contain child pages and leaf
bundles (index.md) for terminal pages with resources.
Adding docs and blog posts
Create every maintained English page and its Chinese peer in the same directory:
guide.md
guide.zh.md
For bundle pages, pair index.md with index.zh.md. Keep routing metadata,
dates, weights, aliases, and resource declarations aligned unless a
language-specific difference is intentional.
Organizing your documentation
Use directories to reflect the reader’s information architecture, not the
implementation’s package tree. Each documentation subsection needs an
_index.md and an _index.zh.md. Child pages appear in the sidebar ordered by
weight, then by the configured fallback ordering.
Prefer a shallow hierarchy. Split a page when it serves a distinct task or audience; do not split merely to shorten a file. See Organizing Your Content.
Docs section landing pages
A docs _index.md renders child-page summaries by default. Use:
simple_list: true
to render a compact list, or:
no_list: true
to suppress the generated list. Give each language variant a localized title and description, and keep the structural option identical.
Organizing your blog posts
Blog posts can live directly below blog/ or in year/category directories. OINK
uses dated directories and pairs each article:
blog/2026/
├── oink-release.md
└── oink-release.zh.md
A post normally supplies:
---
title: OINK 1.0
description: A local-first Docsy distribution.
date: 2026-08-08
author: OINK maintainers
tags: [release]
---
Keep the publication date and author identity consistent across translations. Translate the title, description, taxonomy labels, caption text, and body. Do not translate commit IDs, release tags, commands, or URLs.
Working with top-level landing pages
Default-layout pages are suitable for the home page, product overview, and other destinations that do not need the docs sidebar.
Customizing the example site pages
The bundled home page is content/_index.md with content/_index.zh.md as its
translation. It uses the same local assets and theme pipeline as the rest of
OINK. Change content and project assets in the site; do not edit vendored
runtime files merely to alter branding.
Building your own landing pages
Compose landing pages from standard Markdown and
blocks/* shortcodes. Keep essential
information in text, make call-to-action links meaningful, and test the page at
mobile and desktop widths in both languages.
Adding a community page
Create community/_index.md and community/_index.zh.md. The community layout
uses params.links.user and params.links.developer:
params:
links:
user:
- name: User forum
url: https://community.example.org/
icon: fa-solid fa-comments
desc: Ask questions and share solutions
developer:
- name: GitHub
url: https://github.com/pgsty/oink
icon: fa-brands fa-github
desc: Source, issues, and pull requests
Entries may set rel; OINK also adds noopener to external HTTP links where
appropriate. Set params.contributingUrl in the community page front matter if
the contribution guide is not at the conventional docs route.
Adding static content
Files below static/ are copied to the published root without Markdown
rendering or fingerprinting:
static/reference/api/index.html
is published as /reference/api/index.html. Use this for externally generated
reference sites, verification files, and downloads that require stable names.
Prefer page resources or Hugo Pipes for assets that need resizing,
fingerprinting, or bundle-relative lookup.
OINK’s browser runtime is intentionally shipped from the theme or site itself.
When adding a library, vendor and pin it, record it in theme/VENDOR.json, and
do not introduce an implicit CDN fallback.
RSS feeds
Hugo creates feeds for the home page and list sections. Disable them globally only when the site has no feed consumers:
disableKinds: [RSS]
If a section declares custom outputs, retain RSS explicitly:
outputs:
section: [HTML, RSS, print]
Check the generated language-specific feed URLs and ensure titles, summaries,
dates, canonical URLs, and hreflang relationships are correct.
Sitemap
Hugo generates sitemap.xml by default. Site-wide settings are:
sitemap:
changefreq: monthly
filename: sitemap.xml
priority: 0.5
A page can override these values:
---
title: Release notes
sitemap:
priority: 0.8
---
Treat changefreq and priority as hints, not promises. Exclude drafts,
private material, and noncanonical duplicates before deployment, then inspect
the generated sitemap for every published language.
2.2 - AI-agent support
Features described in this page are experimental, and are useful for early adoption and evaluation. Output details and validation coverage may change in future releases. To track the phased evolution of the agent-support feature, see Improve support for AI-agent doc consumption #2614.
Features
When your site opts in, these are the user-facing and machine-readable behaviors Docsy enables:
- Markdown output format support. Your project’s
outputsconfiguration controls which page kinds publish Markdown. - Discovery: page HTML headers include
rel="alternate"links to the Markdown version of the page. - View Markdown: page meta area includes a View Markdown link to the Markdown version of the page.
llms.txt: site-root file listing.
The remainder of this page explains how to enable each feature, and discusses validation and metrics supported with examples.
Enable Markdown output
Hugo comes with several built-in output formats, including markdown. To
enable Markdown output, add markdown to the Hugo outputs configuration for
the page kinds you want to support. For example:
outputs:
home: [HTML, markdown]
page: [HTML, markdown]
section: [HTML, RSS, print, markdown]
[outputs]
home = [ "HTML", "markdown" ]
page = [ "HTML", "markdown" ]
section = [ "HTML", "RSS", "print", "markdown" ]
{
"outputs": {
"home": ["HTML", "markdown"],
"page": ["HTML", "markdown"],
"section": ["HTML", "RSS", "print", "markdown"]
}
}
Opt pages out
By default, Hugo’s outputs map (whether in multi-file site config or page
front matter) is a full replacement for each page kind, not a merge 1.
When you add markdown, keep every format your site already relies on – for
example RSS and print on sections as is shown in the examples above.
To opt pages out of Markdown output, set outputs in page front matter to
HTML only, or whatever your page’s default output formats are while excluding
markdown. For example:
---
title: HTML-only test page
outputs: [HTML]
---
...
Enable llms.txt
The llms.txt format is a simple text format for listing machine-readable links
to site content. It is designed to be easy for agents to discover and parse, and
to complement the richer but more complex Markdown outputs. To learn more, see
llmstxt.org.
Docsy generates llms.txt at the site root, and includes links to the home
page, main menu pages, and Markdown alternates where they exist. To enable it,
add LLMS to the Hugo outputs configuration for the home page. For example:
outputs:
home: [HTML, markdown, LLMS]
page: [HTML, markdown]
section: [HTML, RSS, print, markdown]
For an example of the generated llms.txt for this site, see
/llms.txt.
Customize output
Docsy renders Markdown output via layouts/all.md and generates llms.txt
via layouts/index.llms.txt. You can override these defaults at several levels:
- Per kind — Add templates such as
home.mdor_default/single.mdunderlayouts/in your project to tailor Markdown output for specific Hugo kinds. - Per shortcode — Add output-format-specific shortcode templates to project-local shortcodes so they emit Markdown-friendly content when appropriate.
- Per page — Provide page-specific content or structure for high-value pages that need a curated agent-facing view.
Server-side support
While outside the scope of Docsy’s support, sites can facilitate agent discovery
and access to Markdown content by implementing server-side content negotiation.
For example, honoring Accept: text/markdown on the same URL as HTML.
Validation and metrics
We use AFDocs to assess basic structural support for agent-facing content,
and to validate that generated outputs meet the configured checks. We also
encourage sites to implement their own monitoring and metrics on agent access
patterns—for example logging requests to Markdown URLs or llms.txt, and
collecting metrics on their use. For details, see
Agent-support checks.
The oink.pgsty.com project contains AFDocs configuration and npm scripts
so maintainers can score a deployed URL against checks that overlap with Docsy’s
agent-support goals, including Markdown URLs, llms.txt, and related categories.
Scorecard examples
For scorecard examples, see:
-
OpenTelemetry agent score online report
-
An AFDocs scorecard for this site:
oink.pgsty.comscorecardRunning in oink.pgsty.com…
Agent-Friendly Docs Scorecard
http://localhost:1313 · 4/26/2026, 5:43:59 AM
Overall Score: 100 / 100 (A+)
Category Scores: Content Discoverability 100 / 100 (A+) Markdown Availability 100 / 100 (A+) Page Size and Truncation Risk 100 / 100 (A+) Content Structure 100 / 100 (A+) URL Stability and Redirects 100 / 100 (A+) Observability and Content Health 100 / 100 (A+) Authentication and Access 100 / 100 (A+)
Check Results:
Content Discoverability PASS llms-txt-exists llms.txt found at 1 location(s) PASS llms-txt-valid llms.txt follows the proposed structure (H1, blockquote, heading-delimited link sections) PASS llms-txt-size llms.txt is 1,131 characters (under 50,000 threshold) PASS llms-txt-links-resolve All 13 same-origin links resolve (13 total links) PASS llms-txt-links-markdown 13/13 same-origin links point to markdown content (100%) PASS llms-txt-directive llms.txt directive found in all 13 pages, near the top of content Markdown Availability PASS markdown-url-support 13/13 pages support .md URLs (100%) PASS content-negotiation 13/13 pages support content negotiation (100%) Page Size and Truncation Risk PASS rendering-strategy All 13 pages contain server-rendered content PASS page-size-markdown All 13 pages under 50K chars (median 2K, max 9K) PASS page-size-html All 13 pages convert under 50K chars (median 2K, 0% boilerplate) Content Structure PASS tabbed-content-serialization No tabbed content detected across 13 pages PASS section-header-quality No tabbed content found; header quality check not applicable PASS markdown-code-fence-validity All 1 code fences properly closed across 14 pages URL Stability and Redirects PASS http-status-codes All 13 pages return proper error codes for bad URLs PASS redirect-behavior No redirects detected across 13 pages Observability and Content Health PASS cache-header-hygiene All 14 endpoints have appropriate cache headers Authentication and Access PASS auth-gate-detection All 13 pages are publicly accessible SKIP auth-alternative-access All docs pages are publicly accessible; no alternative access paths neededFull spec: https://agentdocsspec.com/spec/
For details on how these checks are configured, see Agent-support checks.
-
This is contrary to the documented Hugo behavior for front-matter configuration, but it is confirmed with our testing as of Hugo 0.158.0. ↩︎
2.3 - Analytics, user feedback, and SEO
OINK does not contact analytics, form, comment, or advertising services by default. These integrations are site decisions: enable them explicitly, document the data boundary, and provide any consent or policy required by the site’s users and jurisdiction.
Adding analytics
Hugo provides embedded templates for analytics services. When a site configures Google Analytics, browser usage information such as page views and custom events is sent to Google. This is incompatible with a fully air-gapped runtime and may be incompatible with a strict same-origin Content Security Policy.
Setup
Obtain a Google Analytics measurement ID for the site, then use Hugo’s current service configuration:
services:
googleAnalytics:
id: G-YOUR-ID
Do not also set the deprecated top-level googleAnalytics key. Analytics are
normally emitted only for a production Hugo environment. Build a production
preview and inspect its HTML and browser network log before publication.
If analytics is disabled, OINK emits no Google Analytics request. Remove the configuration entirely rather than inserting a fake identifier.
User feedback
OINK can show a “Was this page helpful?” widget at the bottom of documentation pages. The widget presents Yes and No actions and then displays a configured response, usually with a link to open a documentation issue.
The response can remain useful without analytics: it can direct the reader to an issue template, discussion, email address, or another site-owned feedback channel. Collection and event reporting happen only when the site configures an appropriate destination.
How feedback data is useful
Combine feedback with context instead of treating one score as proof. Pages with high traffic and repeated negative feedback are useful review candidates; highly rated pages can reveal patterns worth testing elsewhere.
Make focused editorial changes when possible. For example, update one stale tutorial, or move a code example earlier on a small group of pages, then compare feedback over an appropriate period. Record releases, traffic shifts, support events, and other factors that could explain the change.
Feedback is directional evidence, not a substitute for user research, accessibility review, support data, or technical validation.
Setup
OINK keeps the widget off by default. Set the global default and configure localized responses. For English:
params:
ui:
feedback:
enable: false
languages:
en:
params:
ui:
feedback:
yes: >-
Glad to hear it! Please <a
href="https://github.com/OWNER/REPOSITORY/issues/new">tell us how we
can improve</a>.
no: >-
Sorry to hear that. Please <a
href="https://github.com/OWNER/REPOSITORY/issues/new">tell us how we
can improve</a>.
For Simplified Chinese, put translated strings in languages.zh.params:
languages:
zh:
params:
ui:
feedback:
yes: >-
很高兴本页对你有帮助!欢迎<a
href="https://github.com/OWNER/REPOSITORY/issues/new">告诉我们如何继续改进</a>。
no: >-
很抱歉本页没有解决问题。请<a
href="https://github.com/OWNER/REPOSITORY/issues/new">告诉我们缺少什么</a>。
Visible response HTML is trusted site configuration. Keep it small, review its links, and do not interpolate untrusted values.
When Google Analytics is configured, the widget can emit a custom page_helpful
event. A positive action uses params.ui.feedback.max_value (100 by default); a
negative action uses 0.
Access feedback data
For Google Analytics, inspect the page_helpful event in the provider’s events
report and create a page-level report when needed. An absent event may mean no
interaction occurred, analytics was blocked or disabled, consent was not given,
or the selected time range is wrong.
Do not enable analytics solely to make the widget visible. A site can keep the response-and-link experience while leaving event collection disabled.
Override feedback on one page
Set feedback in page front matter. The page value overrides the global default
in either direction:
---
title: Feedback example
feedback: true
---
Use feedback: false to hide the widget on a page when the global default is
enabled. For compatibility, hide_feedback: true also hides it when feedback
is not set.
Set the default for all pages
Set the site parameter. OINK defaults it to false; set it to true only when
most documentation pages should show the widget:
params:
ui:
feedback:
enable: false
Add a contact form with Fabform
Fabform and similar hosted form endpoints are optional online services. After creating an account and reviewing its data handling, a site can post a form to its assigned endpoint:
<form action="https://fabform.io/f/{form-id}" method="post">
<label for="email">Your email</label>
<input id="email" name="email" type="email" autocomplete="email" />
<button type="submit">Submit</button>
</form>
Replace {form-id}, translate the visible labels, add a privacy notice, and
provide error and success states. The form will not work offline. A local or
first-party endpoint is preferable when the site must keep submissions within
its own boundary.
Search engine optimization metadata
For each page, OINK chooses the HTML meta description from the first available value:
- the page’s
descriptionfront matter field; - Hugo’s computed page summary for non-index pages;
- the site description in
params.
Write a concise, page-specific description in every language. Do not copy the English description into a Chinese page. Search metadata cannot compensate for thin, duplicated, or inaccurate content.
The theme also emits canonical and alternate-language links from Hugo’s page
translations. Use a correct production baseURL, stable translated routes, and
explicit translated heading IDs. Add other meta tags through the site’s
layouts/_partials/hooks/head-end.html override only when they are not already
provided by the theme.
See Hugo’s Google Analytics configuration, page summaries, and Google’s SEO starter guide for the underlying service and content concepts.
2.4 - Diagrams and formulae
OINK supports KaTeX, Mermaid, Markmap, PlantUML, and Diagrams.net. KaTeX, Mermaid, and Markmap use build-time or same-origin resources shipped with the theme. PlantUML and the Diagrams.net editor require an explicitly configured service endpoint; they do not silently default to a public service.
LaTeX support with KaTeX
KaTeX renders TeX mathematics for the web. Hugo’s embedded KaTeX support can render formulae at build time, so readers do not need a remote math service.
Inline formulae
Inline formulae use the passthrough delimiter pairs configured in Goldmark. Keep surrounding spaces and punctuation outside the formula when possible.
Formulae in display mode
Use a math code block for a formula on its own line:
```math
E = mc^2
```
Activating KaTeX support
math and chem code blocks use theme render hooks automatically. For inline
and delimiter-based formulae, enable Goldmark’s passthrough extension and set
the delimiter pairs appropriate for the site. The included oink.pgsty.com
config shows square-bracket, double-dollar, and parenthesis pairs.
Enable the passthrough extension
The relevant YAML structure is:
markup:
goldmark:
extensions:
passthrough:
enable: true
delimiters:
block: []
inline: []
Fill the arrays with Hugo’s documented delimiter pairs. Choose pairs that do not conflict with the site’s prose or code and apply the setting consistently in every build environment.
Add the passthrough render hook
For delimiter-based math, create layouts/_markup/render-passthrough.html in
the site:
{{ partial "scripts/math.html" . }}
The hook can be scoped to a content type or section by placing it under the corresponding layout directory. A scoped hook avoids treating unrelated content as mathematical passthrough.
Chemical equations and physical units
Hugo’s embedded KaTeX supports the mhchem extension. Use chem code blocks
for chemical equations. The same extension supports physical units. See the
mhchem manual for its equation and unit syntax.
Diagrams with Mermaid
Mermaid turns a text definition into a diagram in the browser. Use a
mermaid code block:
```mermaid
flowchart LR
Source --> Hugo --> Static
```
flowchart LR Source --> Hugo --> Static
The theme detects the block, publishes its pinned local Mermaid runtime, and loads it once on that page. Pages without Mermaid do not load the runtime.
Site-wide Mermaid settings live under params.mermaid:
params:
mermaid:
theme: neutral
flowchart:
diagramPadding: 6
Per-diagram front matter can override supported Mermaid settings. Keep diagram text readable in source, test both color modes, and provide surrounding prose for information that must remain accessible when a diagram cannot render.
UML diagrams with PlantUML
PlantUML supports sequence, use-case, class, state, and other UML-oriented
diagrams. A plantuml block contains the source:
```plantuml
actor Reader
participant Browser
participant "PlantUML endpoint" as Server
Reader -> Browser: Open page
Browser -> Server: Request encoded diagram
Server --> Browser: SVG
```
PlantUML requires a renderer endpoint. Enable it only with an approved local or explicit remote service:
params:
plantuml:
enable: true
theme: default
svg_image_url: https://plantuml.internal.example/plantuml/svg/
svg: false
The endpoint receives encoded diagram source from the browser. Review its confidentiality, availability, CSP, and offline implications. For an air-gapped site, use an internal endpoint or commit pre-rendered images; do not point the default configuration at a public demo server.
Mind-map support with Markmap
Markmap converts a Markdown outline into an interactive mind map:
```markmap
# Local-first
## Build
- Hugo Extended
## Browser
- Local scripts
- Local fonts
```
# Local-first
## Build
- Hugo Extended
## Browser
- Local scripts
- Local fonts
Enable the feature globally when desired:
params:
markmap:
enable: true
The runtime is pinned and served locally. Keep the underlying outline useful and avoid relying on pointer-only interactions.
Diagrams with Diagrams.net
Diagrams.net (draw.io) can export SVG and PNG files that retain an
embedded copy of their editable diagram. OINK can detect those images and show
an Edit action when an editor endpoint is explicitly configured.
params:
drawio:
enable: true
drawio_server: https://drawio.internal.example/
Export with Include a copy of my diagram enabled. The page can display the exported image offline, but opening the editor requires the configured service. Saving in the editor downloads an updated file to the browser; it does not write directly to the documentation repository.
Treat a public Diagrams.net endpoint as an online integration. If editing must
stay inside an organization, deploy an approved self-hosted editor and set
drawio_server to it.
Resource and authoring checklist
- Use text-based diagrams when reviewable diffs are valuable.
- Provide alt text or adjacent prose for essential meaning.
- Test light, dark, mobile, print, and reduced-motion behavior.
- Keep local runtimes pinned in
theme/VENDOR.jsonand load them only when used. - Never include secrets in diagram source sent to a service endpoint.
- Use pre-rendered output when an online renderer is unacceptable.
- Verify all asset and endpoint URLs under a subpath
baseURL.
2.5 - Logos and Images
Add your logo
By default, Docsy shows a site logo at the start of the navbar, that is, at the
extreme left. Place your project’s SVG logo in assets/icons/logo.svg. This
overrides the default Docsy logo in the theme.
If you don’t want a logo to appear in the navbar, then set site parameter
navbar_logo to false in your project’s config:
[params.ui]
navbar_logo = falseparams:
ui:
navbar_logo: false{
"params": {
"ui": {
"navbar_logo": false
}
}
}For information about styling your logo, see Styling your project logo and name.
Use icons
Docsy includes the free FontAwesome icons by default, including logos for sites like GitHub and Stack Overflow. You can view all available icons in the FontAwesome documentation, including the FontAwesome version when the icon was added and whether it is available for free tier users. Check Docsy’s package.json and release notes for Docsy’s currently included version of FontAwesome.
You can add FontAwesome icons to your navbar, side nav, or anywhere in your text.
Add your favicons
The theme ships no favicon files, but it discovers and links a set of
conventionally named icons when you supply them:
create your favicon files and put them in your site
project’s static directory so they publish at the site root (where browsers
probe for them). Docsy adds <link> elements inside each page’s <head> for
whichever of these files it finds, in this order:
| File | Link |
|---|---|
favicon.ico |
rel="icon"1 |
favicon.svg |
rel="icon" with type="image/svg+xml" |
favicon-NxN.png |
rel="icon" with type="image/png" sizes="NxN" |
apple-touch-icon.png |
rel="apple-touch-icon" (implicit size 180x180) |
apple-touch-icon-NxN.png |
rel="apple-touch-icon" with sizes="NxN" |
If you have any square-size variants listed above, Docsy adds them in ascending size order.
A modern favicon.ico plus an SVG and an apple-touch-icon.png covers common
browser and platform favicon needs. For anything beyond that:
- Add web app manifest
<link>elements to hooks/head-end.html. - If you need to customize the favicon links themselves, override
layouts/_partials/favicons.html. Make sure you use
relURLso links stay correct when your site’sbaseURLincludes a subpath.
Generate favicons
Don’t have a favicon yet? You can generate favicons from a single image with an online tool such as favicon.io or RealFaviconGenerator.
If you have a source SVG and ImageMagick installed, Docsy also ships a
gen-favicons helper. Save your source SVG as static/favicon.svg – the theme
links it directly – then generate the raster icons alongside it. Run the
command from your site project root.
For an npm package install of Docsy:
npx --no-install gen-favicons static/favicon.svg static/
Otherwise, run:
node DOCSY_THEME_DIR/scripts/gen-favicons/cli.mjs static/favicon.svg static/
For a Git submodule install of Docsy, DOCSY_THEME_DIR is
themes/docsy/theme. For a Hugo module install, it is the directory printed by
go list -m -f '{{.Dir}}' github.com/google/docsy/theme.
For the sizes and other options you can pass, run the command with --help.
Add images
Landing pages
Docsy’s blocks/cover shortcode makes
it easy to add cover images (also known as hero images) to landing pages. The
shortcode looks for an image with the word “background” in the name within the
landing page’s page bundle.
For example, the example site’s landing page content/en/_index.md uses the
image content/en/featured-background.jpg, which is in the same directory –
see the content/en folder on GitHub.
Use the block’s height parameter to set the preferred display height of
the cover container (and therefore its image). For a full viewport height, use
full, along with the td-below-navbar helper class to position the cover
below the navbar:
{{% blocks/cover
title="Welcome to Docsy!"
image_anchor="top"
height="full td-below-navbar"
%}}
...
{{% /blocks/cover %}}
For a shorter image, as in the example site’s About page, use one of min,
med, max, or auto (the image’s natural height):
{{% blocks/cover
title="About the Docsy Example"
image_anchor="bottom"
height="min td-below-navbar"
%}}
...
{{% /blocks/cover %}}
Other pages
To add inline images to other pages, use the
imgproc shortcode. Alternatively, if you
prefer, just use regular Markdown or HTML images and add your image files to
your project’s static directory. You can find out more about using this
directory in
Adding static content.
-
The
.icolink carries nosizes: the file is self-describing (browsers read the frame sizes it contains), so declaring sizes here would only risk drifting from the actual file. When you also supply afavicon.svg, browsers that support SVG favicons (most modern ones) prefer it, and the.icoserves as the fallback. ↩︎
2.6 - Look and Feel
OINK ships a complete visual system built on Bootstrap and Docsy, with local fonts, icons, styles, and browser code. A consuming site can change tokens and project styles without rebuilding a Node dependency tree.
Project styles
Hugo Extended compiles the theme’s SCSS through Hugo Pipes. Project overrides participate in the same bundle, so production builds can minify, fingerprint, and integrity-check one same-origin stylesheet.
Project style files
Override these files in the site’s assets/scss/ directory:
| File | Purpose |
|---|---|
_variables_project.scss |
Variables set before Bootstrap and OINK defaults |
_variables_project_after_bs.scss |
Variables or maps that require Bootstrap definitions |
_styles_project.scss |
Project selectors loaded after the theme’s component styles |
Start with the smallest override:
// assets/scss/_variables_project.scss
$primary: #315f8f;
$secondary: #b4762e;
// assets/scss/_styles_project.scss
.td-content {
--td-content-max-width: 78ch;
}
Do not edit vendored Bootstrap, Font Awesome, or local font files for ordinary branding. A theme update would overwrite those changes and obscure the dependency boundary.
Advanced style customization
OINK’s SCSS import order is:
- Bootstrap functions;
- project variables;
- OINK defaults and Bootstrap;
- post-Bootstrap project variables;
- OINK components and local brand layer;
- project styles.
Use variables or CSS custom properties for stable design decisions. Override a selector only when no token exists, and scope it to the smallest component. Inspect both light and dark output because many colors are theme-dependent.
⚠️ Resetting internal styles
OINK’s internal partials are not a public Sass API. Importing or suppressing individual internal files couples a site to repository layout and import order. If a product needs a fundamentally different shell, override a Hugo layout or maintain a deliberate theme fork instead of resetting the entire stylesheet.
Extra styles
For isolated third-party CSS, publish a local asset from a hook:
{{ $extra := resources.Get "css/extra.css" | minify | fingerprint }}
<link rel="stylesheet" href="{{ $extra.RelPermalink }}"
integrity="{{ $extra.Data.Integrity }}" crossorigin="anonymous">
Put the template in layouts/partials/hooks/head-end.html. Prefer the project
SCSS files when the rules belong to the site’s design system. Never use a remote
stylesheet as an implicit fallback.
Colors and color themes
Bootstrap semantic colors and OINK brand tokens are available throughout the theme. Semantic names communicate intent better than literal colors.
Site colors
Set Bootstrap variables before compilation:
$primary: #315f8f;
$secondary: #b4762e;
$success: #2c7a4b;
$warning: #9a6700;
$danger: #b42318;
OINK’s canonical layer also exposes CSS properties such as --td-brand-elev,
--td-brand-silk, --td-brand-copper, --td-brand-header-bg, and
--td-brand-mark-gradient. Override them on :root and
[data-bs-theme='dark'] as a pair:
:root {
--td-brand-copper: #a66722;
}
[data-bs-theme='dark'] {
--td-brand-copper: #e0a35c;
}
Light/dark color theme and mode support
Color theme is the palette used by a component; color mode is the
site-wide light or dark state. OINK uses Bootstrap’s
data-bs-theme="light|dark" attribute and stores an explicit reader choice in
local browser storage. With no choice, it follows prefers-color-scheme.
Every custom component must define legible states for both modes, including hover, focus, disabled, selected, and code colors. Do not encode meaning by color alone.
Light/dark color modes
The default sample site enables color-mode support and shows the selector:
params:
ui:
showLightDarkModeMenu: true
The selector updates the document before normal interaction to limit a flash of the wrong theme. OINK’s script is local and does not contact an external service.
Choosing themes or color modes for your site
Use the default automatic behavior for most sites. Choose a forced mode only when the complete visual identity has been tested in that mode and readers do not need an alternative. Screenshots are not sufficient: check real text, tables, alerts, forms, diagrams, code, and focus indicators.
How to disable dark mode
To disable dark mode and hide the menu:
params:
ui:
showLightDarkModeMenu: false
The experimental value enable-only (experimental) enables theme-aware styles
without showing a selector. Treat it as transitional because the configuration
surface can change.
How to pick colors with good color-contrast
Meet WCAG contrast requirements in every component state. Test actual computed colors, including translucent layers over images. As a working minimum, normal text needs 4.5:1 contrast and large text needs 3:1; focus and non-text UI indicators also need adequate contrast. Automated tools catch common failures, but keyboard and visual review remain necessary.
Fonts
OINK does not fetch Google Fonts. Open Sans, Chakra Petch, IBM Plex Mono, and
Font Awesome files used by the theme are stored locally. The legacy Sass
variable $td-enable-google-fonts controls the bundled Open Sans faces despite
its historical name.
Set typography in _variables_project.scss:
$td-enable-google-fonts: true;
$font-family-sans-serif: 'Noto Sans SC', 'Open Sans', system-ui, sans-serif;
$font-family-monospace: 'IBM Plex Mono', ui-monospace, monospace;
If you add a font, subset and self-host it, include the required scripts, use
font-display: swap, document its license in theme/VENDOR.json, and test CJK
fallback. Do not make page rendering depend on a font CDN.
CSS utilities
Bootstrap utility classes are available in Markdown with raw HTML and in
layouts. Prefer semantic Markdown and OINK shortcodes for content; use utilities
for small, presentational adjustments that remain understandable at different
breakpoints. Project-wide patterns belong in _styles_project.scss.
Code blocks
OINK supports Hugo Chroma by default and a locally vendored Prism option. Choose one highlighter consistently; enabling both produces duplicate markup or styles.
Code highlighting with Chroma
Chroma runs during the Hugo build and requires no browser highlighter. Use a language identifier:
```go
fmt.Println("hello")
```
Basic Chroma style configuration
Configure markup in Hugo:
markup:
highlight:
guessSyntax: false
noClasses: false
lineNos: false
OINK expects class-based output so light and dark styles can differ. When regenerating a palette, keep the generated CSS local and review it against the brand background.
Light/dark code styles and more
The theme includes separate Chroma palettes under theme/assets/scss/td/chroma/
and applies them by mode. Project overrides should target .chroma beneath the
relevant theme attribute, not hard-code a global background.
Selecting console block content
Use console for terminal transcripts. OINK styles prompts and output for
selection so readers can copy commands without decorative prompt text. Keep
commands and their output on distinct lines, and never rely on color alone to
distinguish them.
Code blocks without a specified language
An unlabelled fence renders as plain code. Use it only when no grammar applies,
and label command sessions as console or bash instead of asking Chroma to
guess.
Copy to clipboard
Copy buttons are enabled for Chroma unless params.disable_click2copy_chroma is
true. Clipboard access requires a secure context in deployed browsers. The
control must remain keyboard accessible and must not copy line numbers or
prompts.
Code highlighting with Prism
Set:
params:
prism_syntax_highlighting: true
to use OINK’s local prism.js and prism.css. This is a compatibility option
for existing sites; Chroma is preferred for a browser-light build.
Code blocks with no language
Prism also treats unlabelled blocks as plain text. Add the correct language class rather than enabling heuristic detection.
Extending Prism for additional languages or plugins
Build and vendor the exact Prism bundle, replace the local files in a controlled theme change, record its version and license, and add a fixture that exercises the language or plugin. Do not pull Prism components from a CDN at runtime.
Navbar
OINK’s navbar contains the project identity, main menu, version and language selectors when applicable, color-mode control, and search. On small screens, overflowing primary items remain horizontally reachable.
Default look and feel
The navbar uses the local brand palette and a fixed minimum height.
On mobile
The brand and actions stay visible while the primary menu can scroll. Test long Chinese labels, 200% zoom, touch targets, focus order, and both page directions.
On desktop
The main menu expands inline; version, language, mode, and search controls stay grouped. Avoid enough custom entries to push controls outside the viewport.
Translucent over cover images
The blocks/cover shortcode marks the navbar as cover-aware. It starts
translucent and gains the normal background as the page scrolls.
Customizing the navbar
Use configuration for behavior and project SCSS for presentation. Preserve the landmark, focus order, accessible labels, and responsive overflow behavior when overriding the navbar partial.
Navbar height
Override $td-navbar-min-height before theme styles compile. Re-test anchor
offsets, sidebar height, mobile wrapping, and cover blocks because all depend on
this value.
Background color/opacity
Set --td-navbar-bg-color or --td-brand-header-bg in both modes. If the
background is translucent, validate contrast over every cover image and provide
a solid scrolled state.
Setting the navbar light/dark color theme
A page can set ui.navbar_theme: dark in front matter or cascade when its cover
requires light foreground controls. This changes navbar component styling; it
does not force the whole site’s color mode.
Translucent over cover images
Disable translucency site-wide with:
params:
ui:
navbar_translucent_over_cover_disable: true
Prefer this when cover imagery is unpredictable or accessibility review cannot guarantee contrast.
Styling your project logo and name
Place logo partial overrides under layouts/partials/ and source assets under
assets/ or static/. Provide meaningful alternative text for informative
marks and an empty alternative for a purely decorative mark. SVGs must use a
view box and inherit or define colors for both modes.
The OINK sample uses a text wordmark with a local gradient. Change the site title in language configuration and the visual tokens in project SCSS; do not replace brand text with an image when selectable text works.
Light/dark-mode menu
The selector appears when params.ui.showLightDarkModeMenu is true. Keep it in
the shared navigation so its state applies consistently across languages and
page types.
Alerts
Markdown alert types map to semantic OINK/Bootstrap styles. Customize .alert-*
and the alert render hook only as a pair, retain a visible label or icon, and
test links and inline code inside every background. See
Adding Content for syntax.
Tables
Markdown tables receive responsive and theme-aware styles. Keep cells concise, use real header cells, add a caption in custom HTML when context requires one, and test horizontal overflow on mobile. A table should not be used to position unrelated content.
Customizing templates
Hugo resolves site layouts before theme layouts. Copy only the smallest partial
that needs changing and compare it during upstream syncs; a full baseof.html
override can silently miss future accessibility and asset-pipeline fixes.
Add code to head or before body end
Use layouts/partials/hooks/head-end.html for head additions and
layouts/partials/hooks/body-end.html for scripts or closing integrations.
Self-host assets, load them only on pages that need them, and keep production
CSP compatible.
Adding a banner before page content
Override the relevant hook or content partial with a condition based on page parameters. A banner must not hide the page heading, trap keyboard focus, or shift anchor targets beneath the fixed navigation.
Adding custom class to the body element
Set body_class in page front matter or a section cascade:
---
body_class: product-reference
---
OINK appends the value to its generated body classes. Use a project-specific, semantic class name and never insert untrusted content into this field.
2.7 - Navigation and Menus
OINK combines Hugo’s content tree and menu model with a documentation workspace: a global navbar, a collapsible and resizable section sidebar, and a collapsible page outline. The same structure works for English, Chinese, and right-to-left languages.
Site navbar
The global navbar is built from Hugo’s main menu plus OINK-generated controls.
Depending on configuration and page type, it can include version, language,
color-mode, and search controls.
Adding main menu entries
Define a menu entry in page front matter:
---
title: Documentation
linkTitle: Docs
menu:
main:
weight: 20
pre: <i class="fa-solid fa-book" aria-hidden="true"></i>
---
Lower weights appear first. A site-level external link is similar:
menus:
main:
- name: GitHub
identifier: github
weight: 50
url: https://github.com/pgsty/oink
pre: <i class="fa-brands fa-github" aria-hidden="true"></i>
Use an identifier for configuration that refers to a menu item. Localize
name or linkTitle in language configuration, but keep identifiers stable.
Version menu
The selector appears when params.versions is configured. Each entry can be a
heading, separator, release, development build, or site variant:
params:
version: v1.0.0
version_menu: v1.0.0
version_menu_pagelinks: true
versions:
- version: v1.1.0-dev
kind: next
url: https://next.example.org/
- version: v1.0.0
kind: latest
url: https://docs.example.org/
version identifies the published site variant and is not necessarily a Git
ref. Commands that require a resolvable tag should use the project’s explicit
release-ref parameter instead. With page links enabled, OINK first tries the
equivalent path on the target version and otherwise uses its configured URL.
Language menu
OINK builds language targets from Hugo’s AllTranslations. When a translated
peer is missing, the target language’s home page is used instead of a broken
URL. One configured language hides the control. With two or more languages, a
click advances to the next language by weight, while hovering for half a second
or focusing the control opens the complete menu. The current site cycles from
English to Simplified Chinese and back. Targets include lang, hreflang,
locale, and text-direction attributes.
Light/dark theme menu
When color-mode support is enabled, the navbar and documentation workspace show a theme control. See Light/dark-mode menu.
Search box
The documentation workspace uses a local search dialog when offline search is enabled. The sidebar button advertises the platform shortcut (Command/Ctrl+K). Online search integrations remain available by explicit configuration. See Search.
Adding icons to the navbar
Use pre or post on a menu entry. OINK includes the free local Font Awesome
assets:
menus:
main:
- name: Source
identifier: source
url: https://github.com/pgsty/oink
weight: 50
pre: <i class="fa-brands fa-github" aria-hidden="true"></i>
post: <span class="visually-hidden"> (external)</span>
Decorative icons need aria-hidden="true"; the link itself must retain a useful
text or accessible label. External links that open a new tab must use
rel="noopener".
Side navigation
The left panel on docs and blog pages is generated from the content hierarchy.
OINK orders entries by weight and uses linkTitle when present. Sections come
from _index.md files; translated sections need a peer _index.zh.md so their
navigation metadata is localized.
Hide a page from the sidebar with:
toc_hide: true
Hide it from a section landing-page summary with hide_summary: true. Set both
only when the page should be absent from both discovery surfaces.
Side-nav options
The common controls are:
params:
ui:
sidebar_menu_compact: true
sidebar_menu_foldable: true
sidebar_menu_truncate: 128
sidebar_cache_limit: 2000
sidebar_search_disable: false
sidebar_width_min: 220
sidebar_width_max: 480
sidebar_item_overflow: ellipsis
sidebar_menu_compactshows the active branch and nearby entries.sidebar_menu_foldablelets readers expand or collapse sections.sidebar_menu_truncatelimits entries and emits a build warning when the limit is too small.sidebar_cache_limitenables shared navigation markup above the configured site size.sidebar_width_minandsidebar_width_maxclamp the desktop drag-resizer.sidebar_item_overflowisellipsisby default; usewrapfor long labels.
The reader’s collapse state, width, and scroll position are preserved locally. The mobile view becomes a dismissible drawer with a backdrop and focus-safe controls.
Adding icons to the side nav
Set icon in page front matter:
---
title: Operations
icon: fa-solid fa-screwdriver-wrench
---
Use icons consistently across siblings. They are secondary cues, not a replacement for text labels.
Adding manual links to the side nav
Create a placeholder page at the desired position:
---
title: API status
weight: 90
manualLink: https://status.example.org/
manualLinkTitle: Live service status
manualLinkTarget: _blank
---
Use manualLinkRelref instead of manualLink for an internal content
reference; Hugo then fails the build if it cannot resolve the destination. OINK
adds noopener for new-tab links. Include a short body explaining the
destination because Hugo still generates a page for the placeholder.
Section as sidebar root (EXPERIMENTAL)
Enable rooted sidebars:
params:
ui:
sidebar_root_enabled: true
sidebar_root_menu: true
Then set a section’s _index.md:
---
title: API Reference v2
sidebar_root_for: self
sidebar_root_link_self: true
---
self applies the root to the section index and descendants; children keeps
the index in the parent tree but roots its descendants. The optional root menu
lets readers switch between roots. Rooted sections can nest, but redundant or
invalid values produce build warnings.
Table of contents (TOC)
Hugo builds the right-side page outline from Markdown headings. OINK renders it as a fixed documentation panel with quick links, language and theme controls, repository metadata, and taxonomy terms. Readers can collapse the panel; its state is stored locally.
Headings emitted by Markdown shortcodes ({{% ... %}}) participate in
Hugo’s table of contents. Headings emitted only by standard shortcodes
({{< ... >}}) generally do not, so content structure should remain in
Markdown whenever possible.
TOC customization
Hide the outline on one page:
notoc: true
Configure which heading levels Hugo includes:
markup:
tableOfContents:
startLevel: 2
endLevel: 4
ordered: false
Localize labels such as toc_on_this_page in the site’s i18n bundle. If custom
CSS changes the outline rail or fixed-panel dimensions, test active tracking,
zoom, keyboard focus, and pages with no headings.
Active TOC entry tracking with ScrollSpy
OINK uses a local Bootstrap ScrollSpy patch and IntersectionObserver to track the active heading. The workspace draws a continuous rail, active segment, and position marker. Disable tracking for a page with:
params:
ui:
scrollSpy:
disable: true
The legacy ScrollSpy configuration also accepts a global rootMargin. Changing
it affects when an entry becomes active and should be tested with short
sections, long sections, and direct fragment navigation.
Advanced ScrollSpy customization
Prefer configuration and project CSS. Overriding the ScrollSpy attribute partial
or docs-shell.js creates an implementation-level fork; add browser fixtures
for hash updates, back/forward navigation, resizing, reduced motion, and pages
that contain duplicate or missing IDs.
Breadcrumb navigation
Breadcrumbs are shown above ordinary content pages and in taxonomy results. Disable them globally:
params:
ui:
breadcrumb_disable: true
taxonomy_breadcrumb_disable: true
The same ui.breadcrumb_disable value can be set in a page or section cascade.
Breadcrumb labels come from localized page titles and must follow the same
logical hierarchy as the sidebar.
Heading self links
Enable OINK’s heading render hook in a consuming site:
{{ partial "td/render-heading.html" . }}
The generated .td-heading-self-link control uses # by default. It remains
visible on touch devices and appears on hover or focus for pointer devices. Keep
the link keyboard reachable and preserve a scroll offset that clears fixed
navigation.
Heading aliases and in-page targets
Changing a heading can break inbound fragment links. Treat its ID as a public route. To rename an ID, retain the old one as an empty anchor and set the new one explicitly:
## Quickstart <a id="get-started"></a> {#quickstart}
Use an empty <a id="..."></a> for an alias or other in-page target. Do not use
a span solely as a fragment target. IDs must be unique, stable, ASCII where
practical, and identical across language variants.
Quickstart
This live heading demonstrates that both #get-started and #quickstart reach
the same location. Translated headings should write the English rendered ID
explicitly rather than relying on language-specific automatic slug generation.
Implementation notes
- The document sets a global scroll offset for fixed chrome.
- Built-in block targets use
td-anchor-no-extra-offsetto avoid applying the additional offset twice. - The translation audit compares rendered heading IDs between English and Chinese pages.
- Removing an old alias is a breaking documentation change and needs a redirect or an explicitly documented compatibility decision.
2.8 - Print Support
Individual documentation pages print well from most browsers as the layouts have been styled to omit navigational chrome from the printed output.
On some sites, it can be useful to enable a “print entire section” feature (as seen in this user guide). Selecting this option renders the entire current top-level section (such as Content and Customization for this page) with all of its child pages and sections in a format suited to printing, complete with a table of contents for the section.
To enable this feature, add the “print” output format in your site’s
hugo.toml/hugo.yaml/hugo.json file for the “section” type:
[outputs]
section = [ "HTML", "RSS", "print" ]outputs:
section:
- HTML
- RSS
- print{
"outputs": {
"section": [
"HTML",
"RSS",
"print"
]
}
}The site should then show a “Print entire section” link in the right hand navigation.
Further Customization
Disabling the ToC
To disable showing the table of contents in the printable view, set the
disable_toc param to true, either in the page front matter, or in
hugo.toml/hugo.yaml/hugo.json:
+++
…
disable_toc = true
…
+++---
…
disable_toc: true
…
---{
…,
"disable_toc": true,
…
}[params.print]
disable_toc = trueparams:
print:
disable_toc: true{
"params": {
"print": {
"disable_toc": true
}
}
}Layout hooks
A number of layout partials and hooks are defined that can be used to customize
the printed format. These can be found in layouts/_partials/print.
Hooks can be defined on a per-type basis. For example, you may want to customize
the layouts of heading for “blog” pages vs “docs”. This can be achieved by
creating layouts/_partials/print/page-heading-<type>.html such as
page-heading-blog.html. It defaults to using the page title and description as
a heading.
Similarly, the formatting for each page can be customized by creating
layouts/_partials/print/content-<type>.html.
2.9 - Repository links and page information
OINK’s documentation and blog layouts can show links to the current page’s source repository:
- View page source opens the source file.
- Edit this page opens an editable source view.
- Create child page starts a new file below the current page and can use the
site’s
assets/stubs/new-page-template.mdtemplate. - Create documentation issue opens an issue against the documentation repository with page context.
- Create project issue optionally targets a separate product repository.
The built-in URL patterns target GitHub-style repositories. Verify every action when using another compatible host, and override the relevant partial for a different URL scheme.
Link configuration
A typical site configuration is:
params:
github_repo: https://github.com/OWNER/DOCS
github_project_repo: https://github.com/OWNER/PRODUCT
github_branch: main
github_subdir: site
The values can be set globally, per language, in a section cascade, or in page front matter when content comes from more than one repository.
github_repo
The documentation source repository URL. It drives view, edit, child-page, and documentation-issue links:
params:
github_repo: https://github.com/pgsty/oink
Omit it to suppress repository-derived page actions. Do not point it at the theme repository when the page source actually lives in a consuming site.
github_subdir (optional)
Set the path from the repository root to the Hugo site source. This project
stores its site in oink.pgsty.com:
params:
github_subdir: oink.pgsty.com
The value is a repository path, not a local absolute path and not the content directory itself unless that is the actual site root.
github_project_repo (optional)
Set a separate product repository to show Create project issue:
params:
github_project_repo: https://github.com/OWNER/PRODUCT
Use the documentation repository for content defects and the product repository for behavior discussed by the page. If that distinction is not clear to readers, omit the second link.
github_branch (optional)
Set the branch used by source and edit URLs:
params:
github_branch: main
This is normally the site’s source branch. It is not necessarily the deployed branch, generated Pages branch, or theme revision.
path_base_for_github_subdir (optional)
Use a section cascade when a subtree is mounted from another repository. The
path base is removed before the remaining content path is appended to
github_subdir:
---
title: Imported reference
cascade:
github_repo: https://github.com/OWNER/UPSTREAM
github_project_repo: https://github.com/OWNER/UPSTREAM
github_subdir: docs
path_base_for_github_subdir: content/reference
---
For a source page at content/reference/api/client.md, this configuration maps
the repository path to docs/api/client.md.
path_base_for_github_subdir can be a regular expression. A language-directory
site might use:
path_base_for_github_subdir: content/\w+/reference
OINK’s colocated .md / .zh.md layout normally uses the same static base for
both languages and does not need the language component in this expression.
When the source file has another name, use a from and to mapping. This
example maps a section _index.md to an upstream README.md:
path_base_for_github_subdir:
from: content/reference/(.*?)/_index.md
to: $1/README.md
Test view and edit links from a leaf page, a section page, and both language versions. A regular expression that removes too much can produce a plausible but incorrect repository URL.
github_url (optional)
github_url is deprecated. Use
path_base_for_github_subdir and the
repository parameters for new content.
A legacy page can set a complete custom edit URL in front matter:
---
title: Imported page
github_url: https://github.com/OWNER/UPSTREAM/edit/main/README.md
---
Pages using this value expose only Edit this page. A site-specific template override is preferable when the destination is not GitHub-compatible.
Disabling links
Each action has a stable CSS class:
| Link | Class |
|---|---|
| View page source | .td-page-meta__view |
| Edit this page | .td-page-meta__edit |
| Create child page | .td-page-meta__child |
| Create documentation issue | .td-page-meta__issue |
| Create project issue | .td-page-meta__project-issue |
Hide an action in assets/scss/_styles_project.scss when the destination does
not support it:
.td-page-meta__child {
display: none;
}
Prefer omitting an unavailable global destination in configuration. CSS hiding is useful for selective policy; it does not make a malformed link correct.
Last-modified page metadata
Enable Hugo Git information and configure the source repository:
enableGitInfo: true
params:
github_repo: https://github.com/OWNER/DOCS
OINK can then show the last commit date, subject, hash, and source link on documentation and blog pages. CI must fetch enough Git history for the current file; shallow checkouts can produce missing or misleading metadata.
To hide the note for a particular site or section, override its style or the responsible page-meta partial. Do not label a file “last modified” from the build timestamp when Git history is unavailable.
2.10 - Search
OINK’s default and recommended search is local. Hugo generates a per-language index; the theme serves Lunr and its CJK fallback from same-origin assets. The site can build and search without a public crawler, external account, CDN, or network connection.
Google Custom Search and Algolia DocSearch remain compatible online integrations. They are disabled by default and should be enabled only when the site accepts their external requests, indexing, availability, and privacy boundaries.
Only one search implementation can be active at a time.
Local search with Lunr
Enable local search in hugo.yaml:
params:
offlineSearch: true
Do not configure gcs_engine_id or params.search.algolia at the same time.
After a production build, the output contains one index per language, for
example:
offline-search-index.en.json
offline-search-index.zh.json
The browser loads the active language’s index and displays results without leaving the page. Chinese content uses OINK’s CJK fallback instead of depending on whitespace tokenization.
Build the index before testing
Run a normal build before starting a preview:
hugo --gc
hugo server --disableFastRender
If the server was already running when the index changed, restart it. On a
subpath deployment, confirm that the browser requests the index under the
configured baseURL rather than from the domain root.
Configure result summaries and limits
Set the summary length and maximum result count:
params:
offlineSearch: true
offlineSearchSummaryLength: 120
offlineSearchMaxResults: 12
Choose limits that keep the search dialog responsive on mobile devices. The summary is a discovery aid, not a replacement for a well-written page description.
Exclude a page
Set exclude_search: true in page front matter:
---
title: Internal index
exclude_search: true
---
Use this for utility, duplicate, generated, or test pages. Do not exclude a page only because its current translation is incomplete; fix the translation instead.
Style the result panel
The result panel grows with its content. A site can constrain it in
assets/scss/_styles_project.scss:
.td-offline-search-results {
max-width: 46rem;
}
Preserve keyboard focus, visible selection, mobile width, and dark-mode contrast when overriding search styles.
Search entry points
OINK exposes search from the branded shell and can also show a sidebar input. To hide the sidebar input while retaining the main search entry, configure:
params:
ui:
sidebar_search_disable: true
The shell’s open and close controls expose their dialog relationship and state to assistive technology. A custom implementation must preserve those semantics.
Multilingual search
Search stays in the active language. Verify that:
- every published language has its own index;
- translated titles, descriptions, and body text appear in that index;
- a result URL contains the correct language prefix;
- English results do not replace Chinese results through content fallback;
- the language selector on a result page reaches the corresponding translation or the documented language-home fallback.
For Chinese search failures, inspect the generated Chinese JSON before changing tokenization. A missing or English-only index is usually a content or build configuration problem.
Google Custom Search (optional)
Google Custom Search Engine (GCSE) searches a public site through Google’s index. It requires a deployed, crawlable production site and sends queries to a third-party service.
After creating an engine in Google Programmable Search, add a search result page:
---
title: Search results
layout: search
---
Then configure its engine ID:
params:
gcs_engine_id: YOUR_ENGINE_ID
offlineSearch: false
Create a translated result page for every supported language and use a
language-appropriate engine configuration when needed. Removing gcs_engine_id
disables GCSE.
Document the external request and privacy implications in the consuming site’s policy. GCSE is not available in an air-gapped deployment.
Algolia DocSearch (optional)
Algolia DocSearch provides a hosted crawler and interactive result panel for eligible public documentation sites. Obtain the project’s application ID, search API key, and index name, then configure:
params:
offlineSearch: false
search:
algolia:
appId: YOUR_APP_ID
apiKey: YOUR_SEARCH_API_KEY
indexName: YOUR_INDEX_NAME
Use a search-only public key, never an administrative key. Keep crawler rules, language facets, index updates, and external-service disclosure with the site configuration. This integration is intentionally separate from the local-first default.
The theme partials layouts/_partials/algolia/head.html and
layouts/_partials/algolia/scripts.html can be overridden for a site-specific
integration. An empty override disables that theme partial.
Custom search
If none of the supported choices fits, a site can replace the search input, result behavior, and styles. Reuse the shell’s dialog and accessibility contracts where possible. Keep custom code at the site layer unless it is provider-neutral and reusable across multiple products.
A custom online provider must be opt-in and document its network, privacy,
indexing, failure, and offline behavior. A custom local provider must publish
all runtime assets from the site or theme and respect language and baseURL
boundaries.
2.11 - Shortcodes
Shortcodes add behavior that ordinary Markdown cannot express. OINK retains the core Docsy components and adds locally served charts, terminal recordings, infographics, carousels, cards, and disclosure widgets. Browser runtimes load only on pages that use them.
Prefer Markdown for headings, prose, lists, links, tables, and images. A shortcode becomes part of the content API: changing its name or parameters can break every page that calls it.
Shortcode delimiters
Hugo supports two forms:
{{< name >}}uses standard delimiters and passes inner content as-is;{{% name %}}uses Markdown delimiters and renders inner Markdown in the surrounding content context.
Use the form documented for the component. Nesting, indentation, and blank lines
matter, especially inside lists and blockquotes. In examples, the /* ... */
escape prevents Hugo from executing the displayed shortcode.
blocks/* shortcodes
Block shortcodes compose full-width landing pages. Their color argument uses
OINK/Bootstrap semantic colors or a project-defined block style. Their height
argument accepts the values documented for each block.
blocks/cover
Creates a hero from the page bundle image matching *background* and optional
*logo*:
{{< blocks/cover title="OINK" subtitle="Local-first documentation"
color="dark" height="max" >}} [Get started](/docs/get-started/){ .btn
.btn-lg .btn-primary } {{< /blocks/cover >}}
image_anchor and logo_anchor control image cropping; byline attributes the
image. Heights are auto, min, med, max, or full. Essential hero text
must remain readable without the background.
blocks/lead
Creates a prominent introductory band:
{{% blocks/lead color="primary" height="min" %}} OINK builds the whole
documentation experience with Hugo Extended. {{% /blocks/lead %}}
The height accepts auto, min, med, max, or full.
blocks/section
Creates a general landing-page band:
{{% blocks/section color="light" type="row" height="auto" %}}
### One section
Use ordinary Markdown inside the block. {{% /blocks/section %}}
type selects the container treatment; height uses the block height values.
Keep heading levels consistent with the page outline.
blocks/feature
Creates one feature cell, normally inside a section:
{{% blocks/feature icon="fa-solid fa-box-archive"
title="Works offline" url="/docs/oink/local-first/"
url_text="Read the design" %}} All required browser assets are pinned and
served locally. {{% /blocks/feature %}}
The icon is decorative; title and link text must carry the meaning.
blocks/link-down
Adds a link from one block to the next. It must be nested inside a block. Set an
explicit id when the generated target must remain stable.
Below-navbar layout correction
Blocks that begin directly below fixed navigation use
td-below-navbar/td-anchor-no-extra-offset to compensate for navbar height.
Reuse these classes rather than adding arbitrary top margins; verify direct
fragment navigation after changing navbar dimensions.
Helper shortcodes
alert
The legacy alert shortcode remains available:
{{% alert title="Compatibility note" color="warning" %}} Prefer Markdown
blockquote alerts for new content. {{% /alert %}}
color maps to a Bootstrap alert suffix. New content should generally use the
Markdown alert syntax described in
Adding Content.
Alerts, indentation, and examples
Keep the opening and closing shortcode aligned with their surrounding list or blockquote. Leave a blank line around block Markdown. If an example must show a shortcode literally, escape its delimiters rather than wrapping an active call in another component.
pageinfo
Renders an informational panel around Markdown:
{{% pageinfo color="info" %}} This page describes a preview interface.
{{% /pageinfo %}}
Use a semantic alert for warnings; pageinfo is intended for contextual page
information.
imgproc
Processes an image from the current page bundle:
{{% imgproc "architecture" Fit "960x540" %}} OINK runtime architecture.
{{% /imgproc %}}
Commands are Fit, Resize, Fill, and Crop. The third argument follows
Hugo image-processing syntax. The inner text becomes a caption, and a resource
params.byline is appended when present. Always provide useful alternative or
adjacent text.
swaggerui
Embeds the locally vendored Swagger UI runtime:
{{< swaggerui src="/openapi.yaml" >}}
Use a same-origin specification for offline and CSP-safe deployments. A remote
src is an explicit network dependency and can expose reader metadata to that
host. Only one Swagger UI instance should be placed on a page with the current
compatibility shortcode.
redoc
Embeds the locally vendored Redoc runtime:
{{< redoc "openapi.yaml" >}}
The first argument is a page-relative, site-relative, or explicit HTTP specification. The optional second argument contains Redoc element options. Treat specification content as reviewed input and test large schemas on mobile.
iframe
Embeds another page:
{{< iframe src="/demo/" name="demo" id="demo-frame"
sandbox="allow-scripts allow-same-origin" >}}
Set a descriptive name, a unique id, a fallback sub message, and the
narrowest viable sandbox. The defaults support width and automatic-height
behavior, but cross-origin documents cannot always be measured. An iframe is a
security and privacy boundary, not a general layout tool.
OINK content components
The following components are additions carried by OINK. Each runtime is pinned
in theme/VENDOR.json and loaded on demand from the same origin.
details
Creates an accessible disclosure:
{{% details title="Show migration notes" closed="false" %}} The body accepts
Markdown. {{% /details %}}
closed defaults to true. Use a concise summary and do not hide mandatory
instructions inside a closed disclosure.
asciinema
Plays an asciinema .cast recording:
{{< asciinema file="casts/install.cast" speed="1.25"
markers="0:Start,18:Verify" fit="width" >}}
Important parameters include theme, autoplay, loop, preload, speed,
startAt, poster, cols, rows, idleTimeLimit, pauseOnMarkers,
markers, and fit (width, height, both, or none). Local recordings
can come from Hugo assets or a site-relative URL. Avoid autoplay, remove secrets
from terminal history, and provide nearby text for essential steps.
echarts
Renders an Apache ECharts options object from JSON or YAML:
{{< echarts height="320px" >}} xAxis: type: category data: [Build, Test,
Publish] yAxis: type: value series:
- type: bar data: [42, 38, 12] {{< /echarts >}}
height must be a safe CSS length; theme selects an ECharts theme and
full=true removes the normal content-width clamp.
JavaScript blocks inside the shortcode are rejected by default. They require
unsafe=true on that call or params.content.echarts_unsafe=true. This opt-in
allows executable content and must never be enabled for untrusted authors.
Prefer declarative JSON/YAML, add an adjacent textual summary, and verify dark
mode.
infographic
Renders the locally vendored infographic DSL:
{{< infographic height="360px" >}} infographic
list-row-simple-horizontal-arrow data items - label Build - label Test - label
Publish {{< /infographic >}}
height is auto or a safe CSS length; full=true removes the width clamp.
The DSL is data, not arbitrary HTML. Provide prose that communicates the same
conclusion when the visualization is unavailable.
doc-cards and nav-cards
Both containers accept cols from 1 through 4. Their child cards accept
title, link, image, alt, icon, desc, accent, and badge:
{{< nav-cards cols="2" >}}
{{< nav-card title="Get started" link="/docs/get-started/"
icon="fa-solid fa-rocket" desc="Build with Hugo {version}." >}} {{< nav-card title="Architecture" link="/docs/oink/architecture/"
badge="Design" >}}
{{< /nav-cards >}}
doc-card/doc-cards share the rendering contract and suit editorial content;
nav-card/nav-cards signal navigation. Description tokens such as {version}
resolve from site parameters. Card images are lazy-loaded; supply meaningful
alt text unless the image is decorative.
doc-carousel
Places doc-card elements in a keyboard-scrollable carousel:
{{< doc-carousel label="Release highlights" >}}
{{< doc-card title="Local assets" >}}No CDN required.{{< /doc-card >}}
{{< doc-card title="Bilingual" >}}Stable English and Chinese
routes.{{< /doc-card >}} {{< /doc-carousel >}}
label names the region for assistive technology. Previous/next buttons are
localized. Do not place information only in an off-screen card; the track must
remain usable without script.
param
Prints a page parameter, falling back through Hugo’s Page.Param rules to site
configuration:
OINK version {{< param version >}}.
A missing parameter fails the build. Use param for scalar display values, not
for injecting unreviewed HTML. The internal _param compatibility shortcode
also performs numbered placeholder replacement for legacy content.
Tabbed panes
Tabs group equivalent representations, such as YAML/TOML/JSON configuration. They must not hide sequential steps or unrelated choices.
{{< tabpane text=true persist=lang >}}
{{< tab header="YAML" lang="yaml" >}} params: offlineSearch: true
{{< /tab >}} {{< tab header="TOML" lang="toml" >}} [params]
offlineSearch = true {{< /tab >}} {{< /tabpane >}}
Selection persistence is local to the browser. persist accepts header,
lang, or disabled. The deprecated persistLang should not be used in new
content.
Shortcode details
text=true renders inner content as prose rather than highlighted code.
right=true aligns tabs to the end. langEqualsHeader=true derives language
identifiers from headers. Pane defaults can be overridden per tab.
tabpane
The parent validates boolean and persistence parameters, builds unique IDs, and ensures a selected tab. Use one disabled header tab only when it adds a useful group label.
tab
tab must be inside tabpane. It accepts header, selected, lang,
highlight, text, right, and disabled. Only one tab should be selected.
Translate reader-facing headers, but keep language identifiers stable.
Card panes
The legacy cardpane/card pair lays out Bootstrap-style cards. New navigation
surfaces should prefer OINK content cards, but existing Docsy content can keep
the compatibility component.
Shortcode card: textual content
{{% cardpane %}}
{{% card header="Note" title="Local build" footer="Verified" %}} Markdown
**content**. {{% /card %}} {{% /cardpane %}}
header, title, subtitle, and footer accept rendered text. Keep equal
cards concise and avoid using cards as a replacement for headings.
Shortcode card: programming code
Set code=true and optionally lang/highlight:
{{< cardpane >}} {{< card code=true header="Go" lang="go" >}}
fmt.Println("OINK") {{< /card >}} {{< /cardpane >}}
Card groups
Adjacent cards in cardpane form a responsive group. Test unequal text length,
mobile stacking, code overflow, and both language variants.
Include external files
The readfile shortcode reads a repository file at build time and either
renders it as Markdown or highlights it as code. The path is relative to the
current content file unless it begins with /.
Reuse documentation
{{% readfile "includes/installation.md" %}}
Included Markdown is not an independent published page and is exempt from the page-pair audit. If shared prose is reader-facing, create and select language-specific include files deliberately; Hugo cannot translate an include.
Installation
Keep reusable fragments under an includes/ directory near their callers.
Document ownership and avoid deep include chains: readers and reviewers should
be able to locate the source quickly.
Include code files
{{< readfile file="includes/config.yaml" code="true" lang="yaml" >}}
code=true highlights the file with lang. Never include secrets, generated
credentials, or untrusted paths.
Error reporting
A missing file fails the build. draft=true replaces that failure with a
visible draft warning, which is suitable only during authoring and must not
reach a release build.
Conditional text
conditional-text selects content using params.buildCondition:
{{% conditional-text include-if="enterprise,preview" %}} This paragraph
appears only in matching builds. {{% /conditional-text %}}
include-if and exclude-if accept condition lists. A condition cannot appear
in both. Use the feature for genuinely different published variants, not for
language selection; multilingual content belongs in translated page files.
2.12 - Taxonomy Support
Docsy supports Hugo taxonomies in its docs and blog section. You can see the default layout and can test the behavior of the generated links on this page.
Terminology
To understand the usage of taxonomies you should understand the following terminology:
-
Taxonomy: a categorization that can be used to classify content - e.g.: Tags, Categories, Projects, People
-
Term: a key within the taxonomy - e.g. within projects: Project A, Project B
-
Value: a piece of content assigned to a term - e.g. a page of your site, that belongs to a specific project
A movie-website sample taxonomy is provided by the Hugo docs.
Parameters
There are various parameters to control the functionality of taxonomies in the
project configuration file. Taxonomies are enabled by default for tags
and categories in Hugo. To disable taxonomies, add the following to your
project config:
disableKinds = ["taxonomy"]disableKinds: [taxonomy]{
"disableKinds": [ "taxonomy" ]
}Then the taxonomy pages for tags and categories will be generated by Hugo.
If you want to use other taxonomies you have to define them in your
configuration file. If you want to use beside your own taxonomies also the
default taxonomies tags and categories, you also have to define them beside
your own taxonomies. You need to provide both the plural and singular labels for
each taxonomy.
With the following example you define a additional taxonomy projects beside
the default taxonomies tags and categories:
[taxonomies][]
tag = "tags"
category = "categories"
project = "projects"taxonomies:
tag: tags
category: categories
project: projects{
"taxonomies": {
"tag": "tags",
"category": "categories",
"project": "projects"
}
}You can use the following parameters in your project’s config to control the output of the assigned taxonomy terms for each article resp. page of your docs and/or blog section in Docsy or a “tag cloud” in Docsy’s right sidebar:
[params.taxonomy]
taxonomyCloud = ["projects", "tags"] # set taxonomyCloud = [] to hide taxonomy clouds
taxonomyCloudTitle = ["Our Projects", "Tag Cloud"] # if used, must have same length as taxonomyCloud
taxonomyPageHeader = ["tags", "categories"] # set taxonomyPageHeader = [] to hide taxonomies on the page headersparams:
taxonomy:
taxonomyCloud:
- projects # remove all entries
- tags # to hide taxonomy clouds
taxonomyCloudTitle: # if used, must have the same
- Our Projects # number of entries as taxonomyCloud
- Tag Cloud
taxonomyPageHeader:
- tags # remove all entries
- categories # to hide taxonomy clouds{
"params": {
"taxonomy": {
"taxonomyCloud": [
"projects",
"tags"
],
"taxonomyCloudTitle": [
"Our Projects",
"Tag Cloud"
],
"taxonomyPageHeader": [
"tags",
"categories"
]
}
}
}The settings above would only show a taxonomy cloud for projects and tags
(with the headlines “Our Projects” and “Tag Cloud”) in Docsy’s right sidebar and
the assigned terms for the taxonomies tags and categories for each page.
To disable any taxonomy cloud you have to set the Parameter taxonomyCloud = []
resp. if you don’t want to show the assigned terms you have to set
taxonomyPageHeader = [].
By default, the plural label of a taxonomy is used as its cloud title. You can
override the default cloud title with taxonomyCloudTitle. But if you do so,
you have to define a manual title for each enabled taxonomy cloud
(taxonomyCloud and taxonomyCloudTitle must have the same length!).
If you don’t set the parameters taxonomyCloud resp. taxonomyPageHeader the
taxonomy clouds resp. assigned terms for all defined taxonomies will be
generated.
Partials
The partials used by default for displaying taxonomies are defined so that you can easily use them in your own layouts.
taxonomy_terms_article
The partial taxonomy_terms_article shows all assigned terms of a given
taxonomy (partial parameter taxo) of an article respectively page (partial
parameter context, most of the time the current page or context .).
Example usage in layouts/docs/list.html for the header of each page in the
docs section:
{{ $context := . }}
{{ range $taxo, $taxo_map := .Site.Taxonomies }}
{{ partial "taxonomy_terms_article.html" (dict "context" $context "taxo" $taxo ) }}
{{ end }}
This will give you for each in the current page (resp. context) defined taxonomy a list with all assigned terms:
<div class="taxonomy taxonomy-terms-article taxo-categories">
<h5 class="taxonomy-title">Categories:</h5>
<ul class="taxonomy-terms">
<li>
<a
class="taxonomy-term"
href="//localhost:1313/categories/taxonomies/"
data-taxonomy-term="taxonomies"
><span class="taxonomy-label">Taxonomies</span></a
>
</li>
</ul>
</div>
<div class="taxonomy taxonomy-terms-article taxo-tags">
<h5 class="taxonomy-title">Tags:</h5>
<ul class="taxonomy-terms">
<li>
<a
class="taxonomy-term"
href="//localhost:1313/tags/tagging/"
data-taxonomy-term="tagging"
><span class="taxonomy-label">Tagging</span></a
>
</li>
<li>
<a
class="taxonomy-term"
href="//localhost:1313/tags/structuring-content/"
data-taxonomy-term="structuring-content"
><span class="taxonomy-label">Structuring Content</span></a
>
</li>
<li>
<a
class="taxonomy-term"
href="//localhost:1313/tags/labelling/"
data-taxonomy-term="labelling"
><span class="taxonomy-label">Labelling</span></a
>
</li>
</ul>
</div>
taxonomy_terms_article_wrapper
The partial taxonomy_terms_article_wrapper is a wrapper for the partial
taxonomy_terms_article with the only parameter context (most of the time the
current page or context .) and checks the taxonomy parameters of your
project’s hugo.toml/hugo.yaml/hugo.json to loop through all listed
taxonomies in the parameter taxonomyPageHeader resp. all defined taxonomies of
your page, if taxonomyPageHeader isn’t set.
taxonomy_terms_cloud
The partial taxonomy_terms_cloud shows all used terms of a given taxonomy
(partial parameter taxo) for your site (partial parameter context, most of
the time the current page or context .) and with the parameter title as
headline.
Example usage in partial taxonomy_terms_clouds for showing all defined
taxonomies and its terms:
{{ $context := . }}
{{ range $taxo, $taxo_map := .Site.Taxonomies }}
{{ partial "taxonomy_terms_cloud.html" (dict "context" $context "taxo" $taxo "title" ( humanize $taxo ) ) }}
{{ end }}
This will give you the following HTML markup for the taxonomy categories:
<div class="taxonomy taxonomy-terms-cloud taxo-categories">
<h5 class="taxonomy-title">Cloud of Categories</h5>
<ul class="taxonomy-terms">
<li>
<a
class="taxonomy-term"
href="//localhost:1313/categories/category-1/"
data-taxonomy-term="category-1"
><span class="taxonomy-label">category 1</span
><span class="taxonomy-count">3</span></a
>
</li>
<li>
<a
class="taxonomy-term"
href="//localhost:1313/categories/category-2/"
data-taxonomy-term="category-2"
><span class="taxonomy-label">category 2</span
><span class="taxonomy-count">1</span></a
>
</li>
<li>
<a
class="taxonomy-term"
href="//localhost:1313/categories/category-3/"
data-taxonomy-term="category-3"
><span class="taxonomy-label">category 3</span
><span class="taxonomy-count">2</span></a
>
</li>
<li>
<a
class="taxonomy-term"
href="//localhost:1313/categories/category-4/"
data-taxonomy-term="category-4"
><span class="taxonomy-label">category 4</span
><span class="taxonomy-count">6</span></a
>
</li>
</ul>
</div>
taxonomy_terms_clouds
The partial taxonomy_terms_clouds is a wrapper for the partial
taxonomy_terms_cloud with the only parameter context (most of the time the
current page or context .) and checks the taxonomy parameters of your
project’s config to loop through all listed taxonomies in the parameter
taxonomyCloud resp. all defined taxonomies of your page, if taxonomyCloud
isn’t set.
Multi language support for taxonomies
For multilingual sites, taxonomy terms get counted and linked within the language site only. Taxonomy config parameters can be adjusted per language.
2.13 - Versioning
Depending on your project’s releases and versioning, you may want to let your users access previous versions of your documentation. How you deploy the previous versions is up to you. This page describes the Docsy features that you can use to provide navigation between the various versions of your docs and to display an information banner on the archived sites.
Adding a version drop-down menu
If you add some [params.versions] in hugo.toml/hugo.yaml/hugo.json, the
Docsy theme adds a version selector drop down to the navbar. You specify a URL
and a name for each version you would like to add to the menu, as in the
following example:
# Add your release versions here
[[params.versions]]
version = "master"
url = "https://master.kubeflow.org"
[[params.versions]]
version = "v0.2"
url = "https://v0-2.kubeflow.org"
[[params.versions]]
version = "v0.3"
url = "https://v0-3.kubeflow.org"params:
versions:
- version: master
url: 'https://master.kubeflow.org'
- version: v0.2
url: 'https://v0-2.kubeflow.org'
- version: v0.3
url: 'https://v0-3.kubeflow.org'{
"params": {
"versions": [
{
"version": "master",
"url": "https://master.kubeflow.org"
},
{
"version": "v0.2",
"url": "https://v0-2.kubeflow.org"
},
{
"version": "v0.3",
"url": "https://v0-3.kubeflow.org"
}
]
}
}Remember to add your current version so that users can navigate back!
The default title for the version drop-down menu is Releases. To change the
title, change the site parameter version_menu in
hugo.toml/hugo.yaml/hugo.json:
[params]
version_menu = "Releases"params:
version_menu: Releases{
"params": {
"version_menu": "Releases"
}
}If you set the version_menu_pagelinks parameter to true, then links in the
version drop-down menu point to the current page in the other version, instead
of the main page. This can be useful if the document doesn’t change much between
the different versions. Note that if the current page doesn’t exist in the other
version, the link will be broken.
You can also configure individual menu entries:
- Use
nameinstead ofversionwhen the menu label is not a version number. - Set
nameto---to add a menu separator. - Omit
urlto render a disabled text item, such as a group heading. - Set
kindto add a kind-specific class for styling. For details, see Navigation and menus. - Set
pagelinks: falseon an entry to link to that version’s main URL even when the globalversion_menu_pagelinksparameter istrue.
For example:
params:
version_menu: v1.2
version_menu_pagelinks: true
versions:
- name: '**Versions**'
- version: v1.3-dev
kind: next
url: https://next.example.com
- version: v1.2
kind: latest
url: https://docs.example.com
- name: ---
- name: Preview variant
kind: home
pagelinks: false
url: https://preview.example.com
To learn more about Docsy menus, see Navigation and menus.
Displaying a banner on archived doc sites
If you create archived snapshots for older versions of your docs, you can add a note at the top of every page in the archived docs to let readers know that they’re seeing an unmaintained snapshot and give them a link to the latest version.
For example, see the archived docs for Kubeflow v0.6:
To add the banner to your doc site, make the following changes in your
hugo.toml/hugo.yaml/hugo.json file:
-
Set the site parameter
archived_versiontotrue:[params] archived_version = trueparams: archived_version: true{ "params": { "archived_version": true } } -
Set the site parameter
versionto the version of the archived doc set. For example, if the archived docs are for version 0.1:[params] version = "0.1"params: version: 0.1{ "params": { "version": "0.1" } } -
Make sure that site parameter
url_latest_versioncontains the URL of the website that you want to point readers to. In most cases, this should be the URL of the latest version of your docs:[params] url_latest_version = "https://your-latest-doc-site.com"params: url_latest_version: https://your-latest-doc-site.com{ "params": { "url_latest_version": "https://your-latest-doc-site.com" } }
3 - OINK
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.
3.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.
3.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.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.
3.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.
3.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.
3.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.
3.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.
3.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/oink@vX.Y.Z
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/oink@vX.Y.Z
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.
4 - Deployment and previews
There are multiple possible options for deploying a Hugo site, including Netlify, Firebase Hosting, Bitbucket with Aerobatic, and more; you can read about them all in Hosting and Deployment. Hugo also makes it easy to deploy your site locally for quick previews of your content.
Build environments and indexing
By default, Hugo sites built with hugo (rather than served locally with
hugo server) have the Hugo build environment production. Deployed Docsy
sites with production builds can be indexed by search engines, including
Google Custom Search Engines. Production
builds also have optimized JavaScript and CSS for live deployment (for example,
minified JS rather than the more legible original source).
If you do not want your deployed site to be indexed by search engines (for
example if you are still developing your live site), or if you want to build a
development version of your site for offline analysis, you can set your Hugo
build environment to something else such as development (the default for local
deploys with hugo server), test, or another environment name of your choice.
The simplest way to set this is by using the -e flag when specifying or
running your hugo command, as in the following example:
hugo -e development
4.1 - Deployment with Amazon S3 and CloudFront
There are several options for publishing your web site using Amazon Web Services. This section describes the most basic option, deploying your site using an S3 bucket and activating the CloudFront CDN (content delivery network) to speed up the delivery of your deployed contents.
-
After your registration at AWS, create your S3 bucket, connect it with your domain, and add it to the CloudFront CDN. This blog post has all the details and provides easy to follow step-by-step instructions for the whole procedure.
-
Download and install the latest version 2 of the AWS Command Line Interface (CLI). Then configure your CLI instance by issuing the command
aws configure(make sure you have your AWS Access Key ID and your AWS Secret Access Key at hand):$ aws configure AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY Default region name [None]: eu-central-1 Default output format [None]: -
Check the proper configuration of your AWS CLI by issuing the command
aws s3 ls, this should output a list of your S3 bucket(s).
-
Inside your
hugo.toml/hugo.yaml/hugo.json, add a[deployment]section like this one:[deployment] [[deployment.targets]] name = "aws" URL = "s3://www.your-domain.tld" cloudFrontDistributionID = "E9RZ8T1EXAMPLEID"deployment: targets: - name: aws URL: 's3://www.your-domain.tld' cloudFrontDistributionID: E9RZ8T1EXAMPLEID{ "deployment": { "targets": [ { "name": "aws", "URL": "s3://www.your-domain.tld", "cloudFrontDistributionID": "E9RZ8T1EXAMPLEID" } ] } }
-
Run the command
hugo --gc --minifyto render the site’s assets into thepublic/directory of your Hugo build environment. -
Use Hugo’s built-in
deploycommand to deploy the site to S3:hugo deploy Deploying to target "aws" (www.your-domain.tld) Identified 77 file(s) to upload, totaling 5.3 MB, and 0 file(s) to delete. Success! Invalidating CloudFront CDN... Success!As you can see, issuing the
hugo deploycommand automatically invalidates your CloudFront CDN cache. -
That’s all you need to do! From now on, you can easily deploy to your S3 bucket using Hugo’s built-in
deploycommand!
For more information about the Hugo deploy command, including command line
options, see this synopsis. In
particular, you may find the --maxDeletes int option or the --force option
(which forces upload of all files) useful.
If the source of your site lives in a GitHub repository, you can use GitHub Actions to deploy the site to your S3 bucket as soon as you commit changes to your GitHub repo. Setup of this workflow is described in this blog post.
If S3 does not meet your needs, consider AWS Amplify Console. This is a more advanced continuous deployment (CD) platform with built-in support for the Hugo static site generator. A starter can be found in Hugo’s official docs.
4.2 - Deployment on GitHub Pages
If your source is hosted on GitHub, GitHub Pages can build and publish the site with a single Actions workflow. The consuming site needs Hugo Extended but does not need Node.js, npm, PostCSS, or a generated deployment branch.
Project sites use a URL such as https://<OWNER>.github.io/<REPOSITORY>/; user
and organization sites use https://<OWNER>.github.io/. Custom domains are also
supported.
Prepare the repository
Push the complete site source to GitHub and confirm that this command succeeds from the repository root:
hugo --gc --minify
Set the site’s baseURL to its production URL, or pass the Pages URL with
Hugo’s --baseURL option in the workflow. A project site must include the
repository path; otherwise CSS, JavaScript, and other resources will resolve
from the wrong location.
Add the Pages workflow
Create .github/workflows/pages.yml with the following contents. Keep
HUGO_VERSION aligned with a version validated by the theme.
name: Deploy Hugo site to Pages
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
env:
GO_VERSION: 1.25.5
HUGO_VERSION: 0.164.0
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
- uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
- name: Install Hugo Extended
run: |
curl -L -o hugo.deb \
"https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb"
sudo dpkg -i hugo.deb
- uses: actions/configure-pages@v6
id: pages
- name: Build
run: >-
hugo --gc --minify --baseURL "${{ steps.pages.outputs.base_url }}/"
- uses: actions/upload-pages-artifact@v5
with:
path: public
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy
id: deployment
uses: actions/deploy-pages@v5
If the theme is installed as a Git submodule, submodules: recursive checks it
out before Hugo runs. A complete offline archive can instead commit or restore
the adjacent theme/ directory as part of the repository or build input.
Enable GitHub Pages
In the repository settings, open Pages. Under Build and deployment, set
Source to GitHub Actions. Push the workflow to main, then follow its
first run in the repository’s Actions tab.
The workflow uploads only the generated public/ directory and publishes it
through the Pages deployment API. It does not maintain a gh-pages branch.
For other authentication, domain, and permission options, see GitHub’s Pages documentation and Hugo’s GitHub hosting guide.
4.3 - Serving your site locally
Depending on your deployment choice you may want to serve your site locally during development to preview content changes. To serve your site locally:
-
Ensure you have an up to date local copy of your site files cloned from your repo.
-
Ensure Hugo Extended and any source-fetch tools required by your chosen installation method are installed, as described in Prerequisites and installation. Node.js and PostCSS are not site-build prerequisites.
-
Run the
hugo servercommand in your site root. By default your site will be available at http://localhost:1313.
Now that you’re serving your site locally, Hugo will watch for changes to the content and automatically refresh your site. If you have more than one local git branch, when you switch between git branches the local website reflects the files in the current branch.
4.4 - Deployment on Netlify
Netlify can build a site from GitHub, GitLab, or Bitbucket and publish a preview for each pull request. An OINK consumer build runs Hugo Extended directly; it does not install Node.js packages or invoke PostCSS.
Configure the site
Push the complete source to your Git provider, import the repository in Netlify, and use these build settings:
| Setting | Value |
|---|---|
| Build command | hugo --gc --minify |
| Publish directory | public |
HUGO_VERSION |
0.164.0 or another theme-validated version |
If Netlify detects package manifests that exist only for theme-maintainer tooling, disable automatic dependency installation for the site. They are not part of the consumer build contract.
For a theme installed as a Git submodule, enable recursive submodule checkout.
For a Hugo module, Netlify also needs the normal Git and Go access required to
download the pinned module on a clean build. A complete offline distribution
uses the adjacent theme/ directory and avoids that first-build download.
Keep configuration in the repository
The same settings can be committed as netlify.toml:
[build]
command = "hugo --gc --minify"
publish = "public"
[build.environment]
HUGO_VERSION = "0.164.0"
Keep production and deploy-preview contexts on the same Hugo version unless a preview is intentionally testing an upgrade. If preview builds need their generated URL as the base URL, add Netlify’s deploy URL to the Hugo command for that context.
To prevent a non-production deployment from being indexed, use a non-production Hugo environment as described in Build environments and indexing.
After saving the settings, trigger a deploy and inspect the build log. A normal consumer build should show one Hugo command and no npm, PostCSS, Autoprefixer, CDN download, or build-time remote-resource step.
4.5 - Page chrome
The theme renders the complete navbar, sidebar, table of contents, search entry, and footer in each applicable page. This is the canonical production structure for normal builds, previews, offline archives, search crawlers, and clients without JavaScript.
The experimental upstream td.chrome = shared donor/restoration mode is not
part of this theme. The params.td.chrome setting has no effect and should be
removed from migrated site configuration. Keeping one server-rendered structure
avoids a second visual implementation and keeps navigation, language selection,
accessibility semantics, and offline behavior deterministic.
Use Hugo minification and hosting-layer compression to reduce transfer size:
hugo --gc --minify
The interactive shell script enhances the already rendered markup; it is not required to reconstruct missing navigation regions.
5 - Multi-language support
OINK uses Hugo’s multilingual page model rather than site-specific domain or
template assumptions. The included site makes English the primary language, and
Simplified Chinese (zh) the second language.
Configure languages
Define the default language and every enabled language in hugo.yaml:
defaultContentLanguage: en
languages:
en:
label: English
locale: en-US
weight: 1
title: Product Documentation
params:
description: Product guides and reference
zh:
label: 简体中文
locale: zh-CN
weight: 2
title: 产品文档
params:
description: 产品指南与参考资料
time_format_default: 2006年1月2日
time_format_blog: 2006年1月2日
weight controls both language ordering and the selector order. label is
shown in that language’s own script. locale supplies standards-friendly
language tags for HTML, alternate links, and Open Graph metadata.
Language-specific parameters override global values; other parameters inherit their global value. Put translated menus under each language when labels differ.
Organize translated content
OINK’s starter colocates translations:
content/docs/
├── install.md
└── install.zh.md
The base name makes the files translations of one page. Keep dates, weights, aliases, resources, and route-affecting metadata aligned unless a deliberate language-specific difference is required.
Translate all visible text, including front matter titles and descriptions, summaries, menu labels, tags, image alternatives, callouts, and shortcode arguments. Preserve commands, identifiers, configuration keys, filenames, URLs, and product names.
Sites with very large independently maintained language trees may use Hugo’s
language-specific contentDir model instead. Do not mix layouts casually: pick
one model, document it, and verify how Hugo associates translations.
Keep heading links stable
Automatic heading IDs depend on the heading text, so translated headings would normally break shared fragment links. Use the English page’s actual rendered ID as an explicit ID in the translation:
## Configure local search
## 配置本地搜索 {#configure-local-search}
Inspect rendered HTML rather than guessing. Inline HTML, punctuation, badges, and shortcodes can affect Hugo’s generated ID. Corresponding pages should have the same heading order and rendered ID list.
Language selector behavior
The selector is generated from Hugo’s configured sites and page translations. It is hidden for a single language. With two or more languages it renders one consistent language button: a direct click advances to the next language by configured weight, while hovering for half a second or focusing the control reveals the complete language menu.
For each target language, the selector links to the current page’s translation
when it exists. If it does not exist, it links to that language’s home page
instead of producing a dead or falsely translated route. The current language
has visible and aria-current state.
SEO and document metadata
Every page emits:
- the correct HTML
langanddirvalues; - its canonical URL;
rel="alternate"links withhreflangfor configured languages;- Open Graph locale and alternate-locale metadata.
Alternate targets follow the same translated-page-or-language-home fallback as
the visible selector. Use a correct production baseURL; subpath deployments
are supported and must not be replaced by hardcoded absolute paths in layouts.
Right-to-left languages
Set direction: rtl on an RTL language:
languages:
ar:
label: العربية
locale: ar
direction: rtl
weight: 4
The theme loads its committed local Bootstrap RTL artifact and uses logical CSS properties in its own shell. LTR and RTL sites use the same command:
hugo --gc --minify
Consumer sites do not install RTLCSS, PostCSS, or npm. Test actual RTL content, navigation, code, tables, diagrams, and mixed-direction strings rather than assuming stylesheet selection is sufficient.
UI translation bundles
Theme UI strings live in theme/i18n/. OINK includes English, Simplified
Chinese, Traditional Chinese, and other inherited bundles. A site can override
only the strings it needs by creating its own i18n/<language>.yaml; remaining
values fall back to the theme bundle.
During translation work, run:
hugo server --printI18nWarnings
Contribute generally useful translations to the theme. Keep product-specific language in the site bundle.
Search by language
With offlineSearch: true, OINK generates a separate same-origin index for each
language. The Simplified Chinese index uses the theme’s CJK fallback. Search
results stay within the active language.
Verify that both offline-search-index.en.json and
offline-search-index.zh.json are generated, contain the expected pages, and
resolve under the deployed baseURL.
Translation checklist
- Every source page in the supported scope has a
.zh.mdpeer. - Front matter identity and route metadata match.
- Visible prose, UI strings, alternative text, and metadata are translated.
- Every translated Markdown heading has an explicit stable ID.
- English and Chinese rendered heading ID lists match.
- Internal links and fragments resolve in both languages.
- Navigation, breadcrumbs, previous/next links, and search stay in language.
- Dates, punctuation, spacing, and technical terminology follow the target language’s editorial conventions.
- The production build emits correct canonical and alternate metadata.
For Hugo’s underlying model, see Multilingual mode.
6 - Update OINK
These pages describe the OINK update contract. A target release is the version that you are moving the site to. Read its release post before starting: it records breaking changes, required actions, and the validated Hugo range.
OINK consumer builds do not install Node.js packages. npm remains repository tooling for theme maintainers, not a site-update step.
Before you update
- Work from a Git branch or another recoverable copy of the site.
- Record the currently pinned theme revision and Hugo Extended version.
- Build the current production site once so that new failures can be separated from pre-existing ones.
- Read every release post between the current and target versions; do not skip intermediate migration actions.
Order of steps
Perform the update in this order:
- Update Hugo if the target release changes its supported range.
- Update the theme using the site’s installation mode.
- Review theme overrides.
- Check the site in development and production builds.
Update Hugo
Install a Hugo Extended version supported by the target release. Update the same version in local developer setup, CI, Cloudflare Pages, Netlify, container images, and any cache keys. Verify the selected binary before building:
hugo version
The current validation baseline is Hugo Extended 0.164.0; the theme’s current
minimum is 0.160.1. A release post takes precedence if it changes either
value.
Update the theme
Use the page matching the site’s installation mode:
For a released archive, replace the existing theme directory with the target
archive only after preserving site-owned overrides. Verify its checksum and keep
LICENSE, NOTICE, and VENDOR.json with the distribution.
Review theme overrides
If the site overrides theme files, compare each override with its new theme counterpart and port relevant changes. Check these directories:
assets/i18n/layouts/static/
Remove an override when the theme now provides the same behavior. Keep site business components, product pages, and brand assets at the site layer.
Check your site
Run both a development preview and the exact production command. For the Hugo-only contract, the production build is:
hugo --gc --minify
Verify at least the following:
- The build completes without errors, warnings, or deprecation notices.
- English and Chinese home, documentation, and blog pages render.
- Navigation, breadcrumbs, table of contents, stable heading links, and language switching resolve correctly.
- Local search returns results in both languages.
- Dark and light modes, mobile navigation, and print output remain usable.
- Pages load only the local runtimes they use; default pages make no theme-owned third-party subresource requests.
- Mermaid, KaTeX, Markmap, Swagger UI, Redoc, and any used content components still render.
- Site-owned shortcodes and business pages remain intact.
Finally, run every release-specific check from the target release post.
6.1 - Update the OINK Hugo module
Pin a version
Production sites should import a release tag or immutable commit, never an unversioned branch. From the site root, update Oink to a specific ref:
hugo mod get github.com/pgsty/oink@THEME_REF
hugo mod tidy
Replace THEME_REF with the published root tag or commit named by the release.
Test a local checkout
To test the current OINK checkout without changing the committed module path, use an ignored Go workspace with the local theme checkout:
go work init .
go work edit -replace=github.com/pgsty/oink=/absolute/path/to/oink
export HUGO_MODULE_WORKSPACE=go.work
hugo --gc --minify
Keep go.work out of version control rather than committing a
developer-specific absolute path.
Verify the resolved module
Inspect Hugo’s dependency graph:
hugo mod graph
Confirm that the theme resolves to the intended tag, commit, or local
replacement. No hugo mod npm pack or npm install step is required for OINK:
browser dependencies already ship with the theme.
Continue with Review theme overrides.
6.2 - Migrate from the Docsy npm package
The upstream @docsy/theme npm package is not an OINK distribution channel.
OINK ships Bootstrap, Font Awesome, fonts, and browser runtimes directly with
the theme so that consumer sites build with Hugo Extended alone.
Remove the npm theme integration
First select an OINK distribution: a versioned archive, Git submodule or clone,
or the compatibility Hugo module. Make that theme available to Hugo and confirm
that hugo --gc --minify can resolve it.
Then remove @docsy/theme and any dependencies used only to build Docsy assets
from the site’s package.json. Remove npm mounts for Bootstrap and Font Awesome
from Hugo configuration, along with PostCSS and Autoprefixer build steps that
exist only for the old theme pipeline.
Do not delete application dependencies merely because they use npm. The Hugo-only contract covers the documentation theme; a site-owned application or business component may have a separate, intentional toolchain.
Verify the migration
Build from a clean checkout with Hugo Extended and no node_modules directory:
hugo --gc --minify
Check LTR and RTL pages if the site supports both. Verify local fonts and icons, search, diagrams, API documentation, and any migrated content components. Once the build is clean, remove obsolete lockfiles only if no site-owned tooling uses them.
Continue with Review theme overrides.
6.3 - Update an OINK Git submodule or clone
Use the procedure matching the installation: submodule or clone. Pin the target release tag or immutable commit in both cases.
Update the submodule
From the site root, fetch tags inside the theme and check out the target ref:
git -C themes/oink fetch --tags
git -C themes/oink checkout THEME_REF
git add themes/oink
git commit -m "Update OINK theme to THEME_REF"
Replace themes/oink if the site uses another directory name. The parent
repository records the resulting submodule commit. Push that parent commit so CI
and other contributors resolve the same source.
No npm installation is required. If the complete theme has nested source-only submodules for a particular release, initialize them according to that release’s notes; browser runtime assets in the OINK distribution are already present.
Update the clone
If the theme directory is a clone tracked or restored by the site, update it to the target ref:
git -C themes/oink fetch --tags
git -C themes/oink checkout THEME_REF
Commit, archive, or record the updated theme using the same reproducible method
the site already uses. Do not leave production builds following main.
If the clone contains local changes, commit them on a branch before switching refs. Rebase or reapply them after the update and resolve conflicts explicitly. Prefer moving reusable changes upstream into OINK and keeping only true site-specific overrides in the consuming site.
Continue with Review theme overrides.
6.4 - Migrate a Docsy site to OINK
This migration removes copied common shell overrides and the consumer-side npm asset pipeline. It does not require a bulk rewrite of Markdown content.
Before you begin
Create a branch and make sure the existing site builds. Inventory custom files
under layouts/, assets/, static/, and i18n/, classifying each as:
- common Docsy shell code now supplied by OINK;
- a reusable component now supplied by OINK;
- a site-owned brand, product page, or business component that must remain.
Do not delete the third category.
Select a theme distribution
Choose a pinned Git checkout, versioned archive, complete offline distribution, or the public Oink Hugo Module. For a temporary local rehearsal, import Oink and use a Go workspace to resolve a local checkout:
go work init .
go work edit -replace=github.com/pgsty/oink=/absolute/path/to/oink
export HUGO_MODULE_WORKSPACE=go.work
hugo --gc --minify
This tests OINK without publishing a developer-specific path in site config or
go.mod.
Remove the consumer asset pipeline
Delete npm mounts and build steps used only to source Bootstrap, Font Awesome,
fonts, or theme browser runtimes. Remove postCSS calls and Autoprefixer steps
that exist only for Docsy. Keep package.json when site-owned software still
needs it, but the documentation build itself must succeed without installing
those packages.
Remove common overrides
OINK directly provides the docs and blog shell, navbar, footer, sidebar, table of contents, search, language selector, head assets, and core content components. Remove matching site overrides one dependency group at a time.
Keep custom homepages, portals, download pages, product data, and business shortcodes until they have an explicit replacement. See the migration guide for the detailed delete/keep matrix.
Verify the result
From a clean checkout with Hugo Extended available, run:
hugo --gc --minify
Check the bilingual page set, local search, dark mode, mobile navigation, print output, diagrams, API docs, content components, and site-specific pages. Inspect the browser network log to confirm that default theme resources are same-origin.
Only after the migrated build and visual review pass should you remove obsolete configuration, lockfiles, or workflow steps.
7 - Best Practices
Use this section to learn about some of the best practices around creating technical documentation with Docsy.
7.1 - Hugo Content Tips
Docsy is a theme for the Hugo static site generator. If you’re not already familiar with Hugo this page provides some useful tips and potential gotchas for adding and editing content for your site. Feel free to add your own!
Linking
By default, regular relative URLs in links are left unchanged by Hugo (they’re
still relative links in your site’s generated HTML), hence some hardcoded
relative links like [relative cross-link](../../peer-folder/sub-file.md) might
behave unexpectedly compared to how they work on your local file system. You may
find it helpful to use some of Hugo’s built-in link shortcodes like
relref to avoid broken links in your
generated site. For example a {{< ref "filename.md" >}} link in Hugo will
actually find and automatically link to your file named filename.md.
Note, however, that ref and relref links don’t work with _index or index
files (for example, this site’s content landing page): you’ll
need to use regular Markdown links to section landing or other index pages.
Specify these links relative to the site’s root URL, for example:
/docs/content/.
7.2 - Organizing Your Content
If you have a look at our Example Site, you’ll see that we’ve organized the Documentation section into a number of subsections, each with some recommendations about what you might put in that section.
Do I need to use this structure?
Absolutely not! The site structure in the Example Site was created to meet the needs of large docsets for large products with lots of features, potential tasks, and reference elements. For a simpler docset (like this one!), it’s fine to just structure your docs around specific features that your users need to know about. Even for larger documentation sets, you may find that the structure isn’t useful “as is”, or that you don’t need to use all the section types.
We do recommend that (as we’ve done here) you provide at least:
- An Overview of the product (either on the docs landing page or a separate Overview page) that tells the user why they should be interested in your project.
- A Getting Started page.
- Some Examples.
You may also want to create some tasks/how-tos for your project’s features. Feel free to copy this Docsy user guide site or even just the docs section instead if you like this simpler structure better.
If you want to copy this guide, be aware that its
source files are
inside the Docsy theme repo, and so it doesn’t have its own themes/
directory: instead, we run hugo server --themesDir ../.. to use Docsy from
its parent directory. You may want to either copy the site and
add a themes/ directory with Docsy,
or just copy the docs/ folder into your existing site’s content root.
Learn more about how Hugo and Docsy use folders and other files to organize your site.
Why this structure?
We based the Example Site structure on our own experiences creating (and using) large documentation sets for different types of project and on user research carried out on some of our bigger sites. In user studies we saw that users cared most about and immediately looked for a Get Started or Getting Started section (so they could, well, get started), and some examples to explore and copy, so we made those into prominent top-level doc sections in our site. Users also wanted to find “recipes” that they could easily look up to perform specific tasks and put together to create their own applications or projects, so we suggest that you add this kind of content as Tasks. Other content types such as conceptual docs, reference docs, and end-to-end tutorials are less important for all doc sets, particularly for smaller projects. We emphasize in our Example Site that these sections are optional.
We hope to improve the Example Site structure further as we learn more about how users interact with technical documentation, particularly for Open Source projects.
Writing style guide
This guide and the example site just address how to organize your documentation content into pages and sections. For some guidance on how to organize and write the content in each page, we recommend the Google Developer Documentation Style Guide, particularly the Style Guide Highlights.
8 - Contribution guidelines
OINK is an independent theme derived from Docsy. Contributions must preserve the Apache-2.0 history and applicable third-party notices while improving the single canonical implementation.
Before opening a change
- Search existing issues and pull requests in the OINK repository.
- For a bug, record the Hugo version, installation mode, language, route, production command, and smallest reproducible input.
- For a feature, explain why it belongs in the reusable theme rather than in a consuming site’s business layer.
- Do not introduce an
oink.enabledswitch, anoink.*configuration tree, or a parallel visual shell. OINK’s standard layouts are the product.
Small fixes can go directly to implementation. Larger behavior changes should state their compatibility, offline, accessibility, security, and migration impact before code is written.
Development environment
Consumer sites need only Hugo Extended, Go, and Git. The theme repository is a direct Hugo Module. The project-site repository uses its pinned Node.js and npm versions for formatting, links, translations, and regression tests.
Install the maintainer dependencies from the repository root using the lockfile. Do not update dependencies as a side effect of an unrelated change.
The project is split across:
github.com/pgsty/oink: published theme source andVENDOR.json;github.com/pgsty/oink.pgsty.com: documentation, examples, and tests.
Build the consumer contract
Always verify the path that users run from a consuming site:
hugo --gc --minify
This build must succeed without npm installation in the consumer site and without a network request for theme-owned browser assets.
For a local theme candidate, clone both repositories as siblings and activate an ignored Hugo workspace:
go work init .
go work edit -replace=github.com/pgsty/oink=../oink
HUGO_MODULE_WORKSPACE=go.work npm run build
Run focused tests
Choose the smallest relevant suite first:
npm run test:hugo-build
npm run test:alt-site
npm run test:md-output
npm run test:favicons
Run npm test for the complete site suite.
Multilingual changes should cover the one-, two-, three-, and four-or-more
language states, missing-page fallback, RTL, canonical URLs, hreflang, and
Open Graph locale metadata.
Content-component changes should cover single and multiple instances, no asset load on unused pages, invalid parameters, subpath builds, print, keyboard use, reduced motion, and offline behavior.
Write bilingual documentation
All user-facing pages added under content/docs/ or content/blog/ need a
.zh.md peer. Follow TRANSLATION.md for terminology and Chinese typography.
Translated Markdown headings use explicit IDs copied from the English rendered HTML. Check source coverage and, after building, rendered heading IDs:
node scripts/check-doc-translations.mjs
node scripts/check-doc-translations.mjs --public public
Preserve code, configuration keys, URLs, release facts, authorship, and link definitions. Translate visible metadata, alternative text, callouts, UI labels, and shortcode strings. Do not submit placeholder or untranslated prose merely to satisfy the filename check.
Preview documentation
Run the project website with the pinned public module, or activate the local workspace described above:
npm run serve
Review English and Chinese versions of the changed pages at desktop and mobile widths. Check light and dark modes, table of contents, language switching, search, code blocks, tables, callouts, print output, and fragment links.
A local build proves only local rendering. CI, release packaging, hosted preview, and production publication are separate verification layers.
Keep changes compatible
- Reuse existing partials, shortcodes, SCSS helpers, and asset loaders.
- Load browser runtimes only on pages that use them, and at most once per page.
- Keep default behavior local-first and same-origin.
- Serialize structured data safely; arbitrary JavaScript requires an explicit unsafe boundary.
- Use logical CSS properties and test LTR and RTL.
- Preserve site-owned business components and documented compatibility aliases.
- Keep legal attribution and vendor metadata with redistributed assets.
Open the pull request
Keep commits and messages lean and explain user-visible behavior and migration impact. Include the focused commands run and their results.
If a change intentionally diverges from Docsy, update the relevant migration or release documentation. Do not remove upstream copyright, license, or history.
9 - About OINK
OINK turns Markdown, configuration, and local assets into a complete technical documentation site. Once a site has the theme source and Hugo Extended, it can build documentation, a blog, multilingual navigation, local search, diagrams, API references, and reusable content components without installing a frontend toolchain.
Start with the documentation, explore the working examples, or read the OINK product reference.
What OINK is
OINK is an independent theme directly evolved from Docsy. It preserves Docsy’s mature Hugo content model while establishing one canonical product surface:
- a branded, responsive documentation shell;
- Hugo-only consumer builds;
- versioned local browser runtimes;
- multilingual routing, metadata, search, and navigation;
- reusable, accessibility-conscious content components;
- a bilingual starter and verifiable offline distribution.
OINK is the project’s current working name. Until a public release defines the final brand, module path, and release coordinates, use an explicit checkout, archive, or immutable revision rather than inferring them from inherited metadata.
What makes technical documentation work
Technical documentation should help readers understand a product and complete a task with as little friction as possible. A useful documentation set is:
- Reliable: statements, commands, versions, and examples match the product.
- Comprehensive: readers can find the concepts, procedures, references, and troubleshooting material appropriate to their role.
- Well organized: related information is grouped consistently and reachable through navigation, search, and stable links.
- Accessible: content, components, color, focus, and keyboard behavior work for a broad audience.
- Maintainable: authors can review, translate, test, and publish changes without a fragile delivery pipeline.
For an international audience, the same information should also remain equivalent across languages. OINK treats language identity, translated routes, stable heading IDs, search indexes, and alternate metadata as infrastructure rather than optional decoration.
What OINK provides
| Capability | What the theme supplies |
|---|---|
| Documentation and blog layouts | Responsive navigation, breadcrumbs, table of contents, page metadata, feedback, print output, and content indexes |
| Automatic navigation | Hugo content structure becomes section navigation without a separate menu manifest |
| Multilingual behavior | Native-language selectors, translated-page routing, home-page fallback, hreflang, locale metadata, and per-language local search |
| Local-first browser features | Bootstrap, Font Awesome, fonts, Mermaid, KaTeX, Markmap, Swagger UI, Redoc, Asciinema, ECharts, and Infographic assets ship with the theme |
| Content components | Tabs, details, cards, navigation cards, carousels, diagrams, terminal recordings, charts, and parameter substitution |
| Site-owned customization | Hugo configuration, menus, content, project SCSS, templates, and business components remain under site control |
| Reproducible delivery | A Hugo-only production command, bilingual starter, vendor manifest, offline archive, migration guide, and automated fixtures |
Author and publish simply
Write content in Markdown or HTML, preview it with Hugo’s local server, then
publish the generated public/ directory through any static hosting platform.
The consumer production command is:
hugo --gc --minify
See deployment options for local, GitHub Pages, Cloudflare Pages, Netlify, and object-storage patterns.
Build and search locally
OINK’s default path performs no build-time download and emits no theme-owned third-party browser request. Search uses a language-specific same-origin index, including a CJK substring fallback for Simplified Chinese. Authors may still configure remote analytics, media, diagram services, or hosted search, but those boundaries are explicit site decisions.
Serve a multilingual audience
Translations can live beside their source pages as page.md and page.zh.md.
The theme derives language switching and SEO metadata from Hugo’s page model.
This site demonstrates English as the primary language, Simplified Chinese as
the second language.
Read multi-language support for content organization, explicit stable heading IDs, RTL behavior, and translation checks.
Customize without forking the shell
Sites own their logo, colors, typography, menus, content, and business-specific components. OINK owns the canonical shell and general-purpose primitives. This separation avoids maintaining a copied layout tree while preserving ordinary Hugo overrides where a real product requirement calls for them.
See look and feel and content components.
Upstream projects and acknowledgements
OINK stands on mature open-source work. Hugo, Docsy, and Fumadocs each influence the project in a different way: Hugo is the build platform, Docsy is the direct upstream, and Fumadocs is a design reference.
| Project | Relationship to OINK | What OINK takes forward |
|---|---|---|
| Hugo | Build platform | Content model, templates, asset pipeline, multilingual routing, taxonomies, and static-site generation |
| Docsy | Direct upstream | Repository history, documentation conventions, layouts, Bootstrap foundation, and compatible APIs |
| Fumadocs | Design reference | A restrained, content-first shell, clear information hierarchy, and polished navigation interactions |
Hugo: the platform
Hugo is the static site generator that builds an OINK site. OINK uses Hugo Extended for content discovery, templates, multilingual pages, taxonomies, asset processing, and static output. OINK is a Hugo theme, not a fork of Hugo.
Docsy: the direct upstream
Docsy is the direct code and content-model upstream of OINK. OINK preserves Docsy’s Apache-2.0 history and attribution, along with the content conventions and compatible APIs that remain useful to existing sites.
OINK is not an optional skin layered over a separate Docsy installation. It develops the inherited theme into an independent product with a standard shell, Hugo-only consumer builds, local browser runtimes, a generalized multilingual model, and additional content components.
Fumadocs: design inspiration
Fumadocs is a React.js documentation framework designed by Fuma Nama. In OINK’s lineage and dependency model, it is a design reference rather than the direct code upstream or build platform.
OINK’s current visual language and documentation shell take inspiration from Fumadocs: its restrained, content-first presentation, information hierarchy, navigation geometry, sidebar interactions, and table-of-contents treatment. OINK reinterprets those ideas for Hugo and the Docsy-derived codebase rather than making a pixel-for-pixel copy.
We thank Fuma Nama and the Fumadocs contributors for sharing their work in the open and for raising the standard of documentation design.
Attribution and boundaries
References to Hugo, Docsy, and Fumadocs describe project lineage, platform dependencies, or design inspiration. They do not imply endorsement. The names and trademarks belong to their respective owners. Source and distributions retain the applicable license and notice files.
Project status and next steps
This checkout is an implementation and documentation preview. A successful local build does not by itself establish a public release, hosted deployment, or stable remote module path.
- Build the bilingual starter.
- Review the architecture and local-first contract.
- Read the implementation diary.
- Follow the release checklist.
- Join the community or read the contribution guide.