This is the multi-page printable view of this section. .
Get started
The recommended path for a new site starts from
pgsty/oink-starter, not from a copy
of this documentation and regression repository. The Starter is a public
GitHub template: it pins OINK v1.0.0, builds as-is, and contains
only neutral project content and deployment workflows.
OINK’s declared compatibility floor is Hugo Extended 0.160.1. The current Starter and its CI use Hugo Extended 0.165.0 and Go 1.27. Use that pinned Starter toolchain for the path below; use the lower floor only when maintaining an existing site that deliberately supports it.
Choose a path
| Starting point | Recommended path | Result |
|---|---|---|
| New documentation or project site | OINK Starter | A small three-language Docs, Blog, and Book site with two deployment workflows |
| Existing Hugo site | From scratch | Add the OINK module and required Goldmark settings without replacing content |
| Existing Docsy or older OINK site | Upgrade | Preserve content, migrate supported syntax, and review site overrides |
Five-minute baseline
-
Install the tools
Install Git, Go 1.27 or newer, and Hugo Extended 0.165.0 or newer. The Hugo output must contain
extended:On macOS,
brew install git go hugosupplies them. On Linux and Windows, use the official Hugo installation guide and Go downloads; choose Hugo Extended. -
Create or clone the site
For a repository you intend to keep, open the Starter and select Use this template, then clone the repository GitHub created for you. To evaluate the untouched original locally:
-
Open the baseline
Open http://localhost:1313/. The default Starter also publishes Chinese at
/zh/and French at/fr/. Confirm that Docs, Blog, Book, search, language switching, and light/dark mode all work before editing anything. -
Make one visible change
Change the title and canonical URL at the top of
hugo.yaml, then edit one sentence indata/home/en.yaml. A browser reload that shows both changes is the first useful proof that configuration, content, and the pinned theme are connected correctly.
Customize from shallow to deep
- Use OINK Starter — identity first, then languages, home page, content, navigation, brand, integrations, and deployment.
- Starter repository tour — which file owns each part of the site, what to replace, and what can be removed.
- Writing pages — front matter, headings, links, images, drafts, and the page-end controls.
- Components — add expression only after the content tree is stable.
- Brand and appearance — logo, accent, typography, width, and CSS extension points.
- Deploy — use the supplied GitHub Pages or Cloudflare Pages workflow, then verify the real public routes.
This order is deliberate. A site that first proves its build and content tree is easier to debug than one that changes languages, navigation, CSS, analytics, and hosting at the same time.
Publication gate
Before the first push, run the same warning-strict production build the Starter workflows use:
Success means the command ends with Total in …, prints no warning or error,
and public/ contains the language roots and representative Docs, Blog, and
Book routes. It does not yet prove deployment: a local build, a commit, a push,
a green workflow, and correct public rendering are separate gates.
Next
Start with the complete Starter tutorial. If the template deliberately carries more structure than your project needs, use the repository tour to remove it safely. Use From scratch only when adding OINK to an existing site or when you explicitly want to assemble every file yourself.
1 - Use OINK Starter
pgsty/oink-starter is the supported
starting point for a new OINK site. It is deliberately smaller than
oink.pgsty.com: no theme documentation, analytics account, comment
repository, browser regression suite, or PGSTY-specific brand is copied into
your project.
The current template pins OINK v1.0.0, Go 1.27, and Hugo Extended 0.165.0. Its default three-language, English-only, and English–Chinese profiles have all been built warning-strictly against that release.
What the template contains
| Surface | Included baseline | First decision |
|---|---|---|
| Languages | English, Simplified Chinese, French | Keep all three, or select a supplied single/bilingual profile |
| Content | Docs, Blog, and a short Book tutorial | Rewrite the examples; delete a whole surface only when you do not need it |
| Home | One compact data/home/<lang>.yaml per language |
Replace the project promise and destinations |
| Brand | Neutral logo and favicon | Keep them until real project artwork exists |
| Integrations | Repository, Giscus, analytics, share, and feedback examples are commented | Enable only complete configurations you intend to operate |
| Deployment | GitHub Pages and Cloudflare Pages Direct Upload workflows | Choose one production path and verify its real URL |
The Starter’s own Book at /book/ is a four-chapter tour from preview to
deployment. This page is the maintainer-grade version: it explains the order of
changes, the boundaries between them, and the checks after each layer.
Create your repository
GitHub template, recommended
Open the Starter repository, select Use this template → Create a new repository, then clone the repository created under your account or organization:
This gives your site its own Git history and keeps the original Starter as an upstream reference rather than as a remote you might accidentally push to.
Clone the original to evaluate it
For a disposable local evaluation:
Do not start a real project by deleting this clone’s .git directory. GitHub’s
template operation already creates the clean project boundary and preserves an
auditable first commit.
Preview before changing anything
Open these routes:
/,/zh/,/fr/— the three home pages;/docs/,/blog/,/book/— the three content surfaces;- one translated page, then the language switcher;
- search and the light/dark control at a narrow viewport.
Also record the resolved module:
It should resolve github.com/pgsty/oink@v1.0.0. This unchanged
preview is the baseline against which every later edit is judged.
Customize in layers
Layer 1: identity
Change the two marked values at the top of hugo.yaml:
The YAML anchor carries the title to all enabled languages. Then change the copyright holder and, after the new repository exists, uncomment its links:
Run hugo server again and check the browser title, footer, edit/history links,
and canonical URL. Do not change the logo yet unless the project has final
artwork; text identity is easier to review first.
Layer 2: language profile
The root configuration enables English, Chinese, and French. Before making other configuration edits, choose one of the supplied profiles when that is not your intended language set:
These are complete minimal configurations, not fragments: copying one replaces
the commented integration examples in the root file. Do it at the beginning;
if hugo.yaml already contains project changes, merge the languages and
disableLanguages sections instead of overwriting it.
Disabled languages stay declared so Hugo recognizes .zh.md and .fr.md as
translations and safely ignores them. If you remove a language permanently,
remove its content and home data only after the selected profile builds.
Layer 3: home page
The home page is data rather than an opaque layout override:
Edit one language first. In each file, sections fixes the order; hero,
cards, and cta provide the content. Replace the promise, destination URLs,
and sample card copy while keeping the structure. After the first language is
right, translate the same information into the enabled peers.
For another composition, use the full registry in Home and landing pages; do not copy the Starter home partial, because there is no site-specific template to copy.
Layer 4: content and navigation
Rewrite or remove sample leaf pages under content/. Keep section roots until
you decide whether that whole surface belongs in your project:
The content tree becomes the sidebar. Top navigation lives in menus.main on
the translated _index roots, so renaming Docs, Blog, or Book happens beside
the content it names rather than in a second global menu tree. Keep translated
files side by side and give corresponding headings the same explicit IDs:
Follow Organizing content before creating a custom navigation data file; the generated tree is enough for most sites.
Layer 5: brand and reader features
Replace assets/icons/logo.svg and static/favicon.svg when real assets are
ready. Then enable the smallest useful configuration changes, one at a time:
For custom local fonts, use params.ui.fonts for family names or declare font
files in site CSS. For layout, sidebar, search, and component settings, consult
the Configuration reference rather than copying the
much larger configuration of oink.pgsty.com.
Layer 6: integrations
The Starter leaves repository actions, Giscus, Google Analytics, feedback, and sharing off or commented. Enable an integration only after all of its required facts are known:
- repository links need the real owner, repository, and branch;
- Giscus needs its repository/category names and immutable IDs;
- Google Analytics needs a project-owned measurement ID;
- feedback records structured
gtagevents only when analytics is present; - assistant links send the current URL to a third party and therefore require an explicit policy choice.
An incomplete optional block should remain commented. See Comments, Analytics and SEO, and Repository links for the operating boundary of each integration.
Build and deploy
Strict local build
Before enabling a hosting workflow:
Commit hugo.yaml, go.mod, and go.sum; never commit generated public/,
resources/, module caches, or a local module replacement.
GitHub Pages
The Starter already contains .github/workflows/github-pages.yaml. In
Settings → Pages, select GitHub Actions as the source. A push to main
builds with the pinned toolchain, asks GitHub for the correct project subpath,
and publishes public/ through the Pages deployment API.
Cloudflare Pages
The supplied .github/workflows/cloudflare-pages.yaml uses Direct Upload.
Create a Pages Direct Upload project, add CLOUDFLARE_ACCOUNT_ID and
CLOUDFLARE_API_TOKEN, then run the workflow manually once. Set the repository
variable CLOUDFLARE_PAGES_ENABLED=true for automatic deploys, and
CLOUDFLARE_SITE_URL when the canonical address is not the default
pages.dev domain.
Use either Direct Upload or Cloudflare Git integration for one project, not
both. The complete host comparison and baseURL rules are in
Deploy.
Verify and remove samples
Before calling the site ready:
- Search for placeholders such as
Project Name,example.org,OWNER, andPROJECT, then decide whether each remaining occurrence is intentional. - Open every enabled language root and representative Docs, Blog, and Book pages on desktop and mobile.
- Confirm language switching lands on peers, not the home page.
- Test search, dark mode, one component, Markdown output, print, 404, canonical URLs, and repository actions.
- Check the deployed workflow and the public URL separately from the local build.
Delete the sample Book or Blog only after removing its top-menu root and any home-page card that links to it. A warning-strict rebuild after each whole surface is removed keeps failures attributable to one change.
Next
Use the Starter repository tour as a file-level map, then continue with Writing pages and Configuration. For an existing site that should not inherit the Starter’s content model, use From scratch.
2 - Starter repository tour
This page describes the repository created from
pgsty/oink-starter. It is not a tour
of the much larger oink.pgsty.com documentation and regression repository.
The theme source is not copied into either site: go.mod pins it as a Hugo
Module, and Hugo stores the resolved source in the Go module cache.
Top-level map
oink-starter/
- oink-starter/
- hugo.yamlidentity, languages, outputs, parameters, module import
- go.modsite module and exact OINK release
- go.summodule checksums
- examples/
- hugo.single.yamlEnglish-only complete profile
- hugo.bilingual.yamlEnglish + Chinese complete profile
- data/
- home/
- en.yamlone compact landing page per language
- zh.yaml
- fr.yaml
- home/
- content/
- _index.mdlanguage home roots
- _index.zh.md
- _index.fr.md
- docs/Introduction, Get Started, Tutorial, Reference
- blog/posts, design records, release announcements
- book/sequential tutorial about the Starter
- assets/
- icons/logo.svgprocessed project logo
- static/
- favicon.svgcopied unchanged to the site root
- i18n/
- fr.yamlStarter-specific French interface overrides
- .github/workflows/
- github-pages.yamlstrict build and GitHub Pages deployment
- cloudflare-pages.yamlstrict build and Cloudflare Direct Upload
- README.mdoperating summary for repository maintainers
- LICENSEtemplate source license
Generated public/, resources/, .hugo_build.lock, and module caches are
ignored build state, not source.
What to change first
| Path | Responsibility | Initial action |
|---|---|---|
hugo.yaml |
Identity, canonical URL, languages, outputs, theme features, optional integrations | Change the two marked values; choose a language profile before other edits |
data/home/ |
Home-page promise, cards, calls to action | Rewrite every enabled language after one language is approved |
content/ |
All reader-facing material | Replace example leaves; keep a section root until deciding to remove that whole surface |
assets/icons/logo.svg |
Processed logo | Replace only with final artwork |
static/favicon.svg |
Browser icon | Replace together with the logo review |
params.github_* in hugo.yaml |
Edit/history/new-page/issue links | Uncomment only after the destination repository exists |
What to keep
go.modandgo.sum: together they pin and verify OINK v1.0.0. Commit both.- The three Goldmark settings in
hugo.yaml: native Steps, Cards, Fields, image attributes, and Book targets depend on them. outputs: removingmarkdown,LLMS, orprintintentionally removes the corresponding Markdown, agent-index, or print surfaces.fetch-depth: 0in workflows whenenableGitInfostays on: last-modified and contributor facts need repository history.GOWORK: offandHUGO_MODULE_WORKSPACE: offin CI: a developer’s local workspace must not replace the published release being verified.
Optional surfaces
Docs, Blog, and Book are independent top-level surfaces. To remove one safely:
- delete its
content/<surface>/tree; - remove any home-page card or link that targets it;
- confirm no other page links to it;
- run a warning-strict build and inspect the remaining top navigation.
Do not delete only translated section roots: that creates language-specific navigation and fallback behaviour that is difficult to distinguish from a mistake. Remove a surface in all enabled languages or document the asymmetry.
The two configuration profiles under examples/ are optional after the
language decision. They are useful references, but the root hugo.yaml is the
only active site configuration.
Content and navigation
Under Docs and Book, directory structure and weight form the sidebar and
pager sequence. Top navigation comes from menus.main on section roots. A
translated root repeats the same identifier, parent, and weight while
translating visible labels.
The Starter intentionally demonstrates the Documentation System model:
- Introduction explains what and why;
- Get Started gets a new user to a result;
- Tutorial teaches an end-to-end task;
- Reference records exact supported behaviour.
Rename or reshape those sections for the project, but preserve the separation between learning paths rather than mixing every kind of answer into one tree.
Language model
English source files end in .md; Chinese and French peers end in .zh.md and
.fr.md. Home data uses language keys under data/home/. The root profile
declares the languages, their locale, order, and site description.
The single and bilingual profiles keep disabled languages declared. This is intentional: Hugo then recognizes the unused suffixes as translations instead of rendering several files onto one English URL. Copy a profile only before project-specific configuration begins; afterwards merge changes by hand.
Where OINK lives
Two files establish the module boundary:
hugo mod graph shows the resolved version. Production follows the exact tag
in go.mod; a local HUGO_MODULE_REPLACEMENTS value is a development override
and must never be committed or treated as release proof.
Deployment files
The GitHub Pages workflow runs automatically on pushes to main; repository
settings must select GitHub Actions as the Pages source. The Cloudflare workflow
runs manually, or automatically only after the repository variable
CLOUDFLARE_PAGES_ENABLED=true is set. Its required account ID and API token
remain repository secrets.
Keep only the workflows for deployment paths you operate. Cloudflare Direct Upload and Cloudflare Git integration are alternative ownership models for the same project, not two gates to run together.
Safe customization order
- Prove the untouched preview.
- Change identity and select languages.
- Replace one home page and then its translations.
- Replace content and verify navigation.
- Change brand and reader features one group at a time.
- Enable complete external integrations.
- Run the strict production build.
- Deploy, then verify production independently.
Commit between layers when the repository is already yours. Small boundaries make a later regression or rollback attributable to one decision.
Verify
The module graph names the pinned release, the build emits no warning or error,
and Git status contains source edits but no public/ or cache files. Then open
the enabled language roots and one Docs, Blog, and Book route before moving to
deployment.
Related
- Use OINK Starter — the complete layered workflow
- From scratch — add OINK without adopting this content model
- Organizing content — sidebar, pager, and menu authority
- Configuration — every current site parameter
- Deploy — host-specific setup and production checks
3 - From scratch and other install methods
This is the manual alternative to the recommended
OINK Starter. It builds a minimal site in an empty
directory: a small hugo.yml plus one hugo mod get gives a single-language
site you can preview. The cost is that the home page, example content,
deployment workflow, and every component usage are yours to assemble.
An existing Hugo site needs no scaffolding: install the theme module, add the
three Goldmark prerequisites (see Writing hugo.yml), and leave the
content alone. For an existing Docsy site, see Upgrade.
The second half weighs four install methods: Hugo Module, Git submodule, offline archive, and pinned clone. For the current v1.0.0 release path, use Go 1.27 and Hugo Extended 0.165.0 unless an existing site deliberately targets the theme’s lower declared compatibility floor.
From an empty directory to the first page
-
Create the skeleton and fetch the theme
What follows
hugo mod initis your own site’s module path, usually the repository address.hugo mod getwritesgo.modandgo.sum, and both are committed.The newest version number is on GitHub Releases; the
v1.0.0on this page is what this site currently pins. A production site pins a release tag rather than followingmain:@latestis a one-off resolution, not a version policy. -
Writing
hugo.ymlRename the
hugo.yamlthathugo new sitegenerated tohugo.yml(Hugo accepts both; this documentation uses the latter throughout) and replace its contents with the following, which builds as it stands:hugo.ymlWhat each of the five blocks governs:
Block Governs Consequence of omitting it Top level + languagesSite name, domain, languages and navbar menu A wrong baseURLsends every absolute link astray in productionmarkup.goldmarkThe three component prerequisites An attribute line becomes a literal {.steps}in the proseparamsSearch, repository links, shell switches Interactive features stay off; the theme does not decide for the site outputsThe per-page .md,llms.txtand print pagesNo “Copy as Markdown” in the page menu, and no print view moduleReferences the theme and declares the Hugo floor The build cannot find the theme Mathematics additionally needs Goldmark’s passthrough extension; see Math. Every key’s full meaning and default is in Configuration.
-
Write the first page
Every top-level directory under
content/is a section, and the directory structure is the sidebar structure. A documentation section needs at least an_index.md:content/docs/_index.mdcontent/docs/install.mdWrite explicit
{#id}anchors on headings: when a translation is added later, the two languages’ anchors have to correspond. How to write a page is in Writing pages. -
Preview
Open http://localhost:1313/ and the sidebar shows Docs → Install. Edits hot-reload in milliseconds.
Other install methods
The steps above use a Hugo Module. The other three address particular
constraints: network isolation, a platform that requires the build input to
contain the whole theme tree, or an organization that reviews its own copy of
the theme. Apart from hugo mod vendor, none of them creates a Go module, and
the site references the theme with theme: oink rather than module.imports.
The shared cost is that version resolution and integrity checking become your
responsibility.
Hugo Module (recommended)
The only method where Hugo resolves the version itself, verifies the checksum,
and leaves an audit record in go.sum. hugo mod graph shows what actually
resolved and hugo mod get -u upgrades. It needs Go on the machine.
Git submodule
Record an exact theme commit in the site repository:
CI must initialize the submodule before running Hugo, or themes/oink is an
empty directory:
Offline archive
For network-isolated environments. Two paths, both prepared on a connected machine and carried in whole.
With hugo mod vendor, the resolved theme source is frozen into the site
directory, and later builds need neither the network nor Go.
When _vendor/ exists Hugo prefers it (hugo mod graph prints +vendor), and
module.imports in hugo.yml stays as it is. This step needs Go; the builds
after it do not. Upgrading the theme means returning to a connected environment
and running hugo mod get and hugo mod vendor again.
_vendor/ collects only the directories the theme mounts (assets, data,
i18n, layouts, static) plus hugo.yaml and theme.toml. It does not
include LICENSE, NOTICE or VENDOR.json. To redistribute that archive, take
those three files from the theme repository as well.
With a tag source archive, no Go module is created; a version of the theme is
simply unpacked into themes/oink/.
The theme repository’s root is the module root, so unpacking lands directly on
layouts/, assets/, i18n/ and static/ with no further level to descend
into. Redistribution must keep LICENSE, NOTICE and VENDOR.json; the last
records each third-party runtime’s version, source, licence path and SHA-256,
and is what an offline audit rests on.
When moving between machines, generate the archive and its checksum from an immutable tag on the connected side:
Carry the archive and its .sha256 into the isolated environment, verify, then
unpack:
An archive produced this way is your own artifact, not a project release. Whether a given tag’s release page carries an archive and a checksum file varies by release; verify the checksum independently when using a public attachment.
Before building offline, confirm the archive is complete. All eleven of these must be present:
themes/oink/
- oink/
- go.modmodule path declaration, used when resolving as a Hugo Module
- hugo.yamltheme default parameters and the Hugo version floor
- theme.tomltheme metadata, required by the theme: oink method
- LICENSEApache-2.0
- NOTICEupstream attribution; must be kept on redistribution
- VENDOR.jsonthird-party runtime manifest: version, source, licence path, SHA-256
- assets/SCSS, JS and the third-party runtimes shipped with the theme
- layouts/templates, partials, shortcodes, render hooks
- static/font files, published as is
- i18n/32 interface language files
- data/the SPDX licence table behind the page-end attribution line
Pinned clone
For a hosting platform that requires the build input to contain the whole theme tree:
The difference from a submodule is that the theme files enter your repository
history directly, without the .gitmodules indirection. Record the commit that
was finally resolved and the procedure for restoring it.
The four methods compared
| Method | Needs Go | Version auditable | Theme source in your repository | Use when |
|---|---|---|---|---|
| Hugo Module | Yes | go.sum verifies automatically |
No | The default |
| Git submodule | No | The repository records the commit | By reference | The theme source has to be in the repository |
| Offline archive | No | Checksums verified by hand | Yes | Network isolation |
| Pinned clone | No | You record it yourself | Yes | The platform requires a complete tree |
Bootstrap, Font Awesome, the fonts, and the search and diagram runtimes all
ship with the theme. A site needs no node_modules, no PostCSS, no RTLCSS and
no CDN. Tutorials that install npm dependencies for a Docsy site describe
upstream Docsy’s process and do not apply to OINK.
Developing against a local theme checkout
This section applies only when changing the theme and the site together. Clone the two repositories as siblings:
Use the HUGO_MODULE_REPLACEMENTS environment variable to substitute the local
checkout temporarily, leaving go.mod untouched:
The documentation site’s Makefile is an alias for exactly these commands, and
make dev and make check expect the theme checkout at the sibling ../oink:
A Go workspace (go work init plus HUGO_MODULE_WORKSPACE=go.work) is an
equivalent alternative. Both apply to the local machine only: CI and production
builds use the version in go.mod, and go.work is never committed.
Verify
It passes when the build ends with Total in … and no WARN or ERROR. Then
confirm:
/docs/opens and the sidebar holds the page you wrote- The navbar has a search box that finds the heading you just wrote
- The light/dark toggle is present, and code block colours follow it (which shows
markup.highlight.noClasses: falsetook effect) git statusshowsgo.modandgo.sum, and nopublic/orresources/
Related
- Get started — choose between Starter, an existing Hugo site, and migration
- OINK Starter — the recommended new-site path
- Starter repository tour — what each template directory owns
- Configuration — every
hugo.ymlkey and its default - Writing pages — how to keep writing after the first page
- Upgrade — upgrading the theme module, and migrating from Docsy