这是本节的多页打印视图。 .
快速上手
- 1: 使用 OINK Starter
- 2: Starter 仓库导览
- 3: 从零建站与其它安装方式
新站点的推荐起点是
pgsty/oink-starter,而不是复制本站这个
文档与回归测试仓库。Starter 是公开的 GitHub 模板:它固定 OINK
v1.0.0,默认即可构建,只包含中性的项目示例与部署 workflow。
OINK 声明的兼容性下限是 Hugo Extended 0.160.1。当前 Starter 与它的 CI 固定使用 Hugo Extended 0.165.0 和 Go 1.27。下面这条路径应 使用 Starter 固定的工具链;只有刻意维护旧环境的既有站点才使用较低的兼容下限。
选择起点
| 当前情况 | 推荐路径 | 得到什么 |
|---|---|---|
| 新建文档站或项目站 | OINK Starter | 一套精简的三语 Docs、Blog、Book 站点与两条部署 workflow |
| 已有 Hugo 站点 | 从零安装 | 不替换内容,只补 OINK 模块与 Goldmark 前置配置 |
| 已有 Docsy 或旧版 OINK 站点 | 版本升级 | 保留内容,迁移受支持的语法,并审查站点覆盖 |
五分钟建立基线
-
安装工具
安装 Git、Go 1.27 或更新版本,以及 Hugo Extended 0.165.0 或更新版本。Hugo 输出必须包含
extended:macOS 可以执行
brew install git go hugo。Linux 与 Windows 请按官方 Hugo 安装指南和 Go 下载页安装,并确认选择 Hugo Extended。 -
创建或克隆站点
准备长期维护时,请打开 Starter 仓库并点击 Use this template,然后克隆 GitHub 为你创建的新仓库。只想在本机评估原始模板时执行:
-
打开基线
打开 http://localhost:1313/。默认 Starter 还在
/zh/发布中文,在/fr/发布法语。开始修改前,先确认 Docs、Blog、Book、本地搜索、语言切换与深浅色 模式都能工作。 -
完成一个可见修改
修改
hugo.yaml顶部的站名与规范 URL,再修改data/home/en.yaml中的一句话。 浏览器刷新后能同时看到两处变化,才算证明配置、内容与固定版本的主题已经正确连通。
由浅入深地定制
- 使用 OINK Starter — 先改身份,再依次处理语言、首页、 内容、导航、品牌、集成与部署。
- Starter 仓库导览 — 每个文件负责什么,哪些要替换, 哪些可以删除。
- 编写页面 — front matter、标题、链接、图片、草稿与页尾控件。
- 组件总览 — 内容树稳定后,再增加表达能力。
- 品牌外观 — Logo、强调色、字体、页宽与 CSS 扩展点。
- 发布上线 — 使用内置 GitHub Pages 或 Cloudflare Pages workflow,再验证真实公开路由。
这个顺序是有意的。先证明构建与内容树,再逐项增加定制,比同时修改语言、导航、 CSS、分析与托管更容易定位问题。
发布门禁
第一次推送前,执行与 Starter workflow 相同的严格生产构建:
命令以 Total in … 结束、没有警告或错误,而且 public/ 中存在各语言根与代表性的
Docs、Blog、Book 路由,才算通过。此时仍只证明本地构建:本地构建、提交、推送、
workflow 变绿与公开站点正确,是彼此独立的关卡。
下一步
继续阅读完整 Starter 教程。如果模板有你不需要的结构, 按仓库导览安全删减。只有在给既有站点接入 OINK,或者 明确想亲手组装每个文件时,才走从零建站路径。
1 - 使用 OINK Starter
pgsty/oink-starter 是新建 OINK
站点的正式起点。它刻意小于 oink.pgsty.com:不会把主题文档、分析账号、评论仓库、
浏览器回归套件或 PGSTY 品牌复制进你的项目。
当前模板固定 OINK v1.0.0、Go 1.27 与 Hugo Extended 0.165.0。 默认三语、仅英文、英中双语三个 profile 都已经在这个版本上完成 warning 即失败的 严格构建。
模板包含什么
| 表面 | 内置基线 | 第一个决定 |
|---|---|---|
| 语言 | 英语、简体中文、法语 | 保留三语,或选择内置单语 / 双语 profile |
| 内容 | Docs、Blog 与一本简短 Book 教程 | 重写示例;确认整个表面不需要时才整棵删除 |
| 首页 | 每种语言一份精简 data/home/<lang>.yaml |
替换项目承诺与入口 |
| 品牌 | 中性 Logo 与 favicon | 有正式项目图形之前先保留 |
| 集成 | 仓库、Giscus、分析、分享、反馈示例均被注释 | 只启用你准备长期运营的完整配置 |
| 部署 | GitHub Pages 与 Cloudflare Pages Direct Upload workflow | 选择一条生产路径并验证真实 URL |
Starter 自己的 /book/ 是一份从预览到部署的四章短教程。本页是维护者级版本:
说明修改顺序、各层边界,以及每层之后应执行的检查。
创建自己的仓库
推荐使用 GitHub 模板
打开 Starter 仓库,点击 Use this template → Create a new repository,再克隆 GitHub 在你的账号或组织下 创建的仓库:
这样站点从一开始就有自己的 Git 历史,原始 Starter 只是上游参考,不会成为一个 可能误推送的 remote。
克隆原始仓库进行评估
只做一次性本地评估时执行:
真实项目不要从删除这个 clone 的 .git 目录开始。GitHub 模板操作已经创建了清晰的
项目边界,并保留可审计的初始提交。
修改前先预览
依次打开:
/、/zh/、/fr/:三个首页;/docs/、/blog/、/book/:三种内容表面;- 任意一组译文,再操作语言切换器;
- 本地搜索、深浅色切换,以及一个窄屏视口。
同时记录实际解析的模块:
结果应当是 github.com/pgsty/oink@v1.0.0。这份未修改的预览,是后面
判断每次改动的基线。
分层定制
第一层:站点身份
修改 hugo.yaml 顶部标有 CHANGE ME 的两个值:
标题的 YAML 锚点会把站名带进所有已启用语言。接着修改版权人,并在新仓库已存在后 取消仓库链接的注释:
重新运行 hugo server,检查浏览器标题、页脚、编辑 / 历史链接与 canonical URL。
项目图形尚未定稿时先不要改 Logo;文字身份更容易先完成评审。
第二层:语言 profile
根配置默认启用英语、中文和法语。如果这不是目标语言组合,请在其它配置修改之前 选择内置 profile:
这两份是完整的最小配置,不是可以叠加的片段;复制会覆盖根文件里那些被注释的集成
示例。因此应在最开始做;hugo.yaml 已有项目修改时,只合并 languages 与
disableLanguages,不要整文件覆盖。
未启用语言仍保留声明,让 Hugo 能识别 .zh.md 与 .fr.md 是译文并安全忽略。
要永久移除一种语言,先确认所选 profile 能构建,再删除对应内容与首页数据。
第三层:首页
首页是数据,不是难以维护的整页模板覆盖:
先改一种语言。每个文件里的 sections 决定顺序,hero、cards、cta 提供内容。
保持结构,替换项目承诺、目标 URL 与示例卡片。第一种语言确认无误后,再把同一组事实
翻译到已启用语言。
需要其它组合时,使用首页与落地页中的完整注册表;不要复制 Starter 的首页 partial,因为这里本来就没有站点自有模板。
第四层:内容与导航
重写或删除 content/ 下的示例叶子页面。确定整个表面不属于你的项目之前,先保留
栏目根:
内容树就是侧栏。顶部导航写在各语言 _index 根页的 menus.main 里,因此给 Docs、
Blog 或 Book 改名时,修改发生在它所描述的内容旁边,而不是另一棵全局菜单树。译文
并排放置,对应标题使用相同的显式 ID:
新增自定义导航数据之前,先读组织内容;大多数站点使用生成树 已经足够。
第五层:品牌与阅读功能
正式图形准备好后,替换 assets/icons/logo.svg 与 static/favicon.svg。随后一次只启用
一组最小而有用的配置:
自定义本地字体时,用 params.ui.fonts 写字体族,或者在站点 CSS 中声明字体文件。
布局、侧栏、搜索与组件配置应查询配置总览,不要复制
oink.pgsty.com 那份大得多的站点配置。
第六层:外部集成
Starter 默认关闭或注释了仓库操作、Giscus、Google Analytics、反馈与分享。只有 必需事实全部明确时才启用:
- 仓库链接需要真实 owner、repository 与 branch;
- Giscus 需要仓库 / 分类名称和不可变 ID;
- Google Analytics 需要项目自己的 measurement ID;
- 反馈只有在分析存在时才记录结构化
gtag事件; - 助手链接会把当前 URL 发送给第三方,因此必须做显式策略选择。
不完整的可选块应继续保持注释。各集成的运营边界见启用评论、 分析与 SEO和仓库与页面信息。
构建与部署
严格本地构建
启用托管 workflow 前执行:
提交 hugo.yaml、go.mod 与 go.sum;不要提交生成的 public/、resources/、模块
缓存或本地模块替换。
GitHub Pages
Starter 已包含 .github/workflows/github-pages.yaml。在
Settings → Pages 中选择 GitHub Actions 作为 Source。推送到 main 后,
workflow 使用固定工具链构建,向 GitHub 查询正确的项目子路径,再通过 Pages 部署
API 发布 public/。
Cloudflare Pages
内置 .github/workflows/cloudflare-pages.yaml 使用 Direct Upload。创建 Pages
Direct Upload 项目,添加 CLOUDFLARE_ACCOUNT_ID 与 CLOUDFLARE_API_TOKEN,再手动
运行一次 workflow。设置仓库变量 CLOUDFLARE_PAGES_ENABLED=true 后才会自动部署;
规范地址不是默认 pages.dev 域名时,再设置 CLOUDFLARE_SITE_URL。
同一个项目只选 Direct Upload 或 Cloudflare Git integration 其中一种。完整托管对比
与 baseURL 规则见发布上线。
验证并删除示例
宣布站点完成前:
- 搜索
Project Name、example.org、OWNER、PROJECT等占位符,逐项确认剩余位置 是否有意保留。 - 在桌面与移动端打开每种已启用语言的根,以及代表性的 Docs、Blog、Book 页面。
- 确认语言切换落到对页,而不是首页。
- 验证搜索、深色模式、一个组件、Markdown 输出、打印、404、canonical URL 与仓库操作。
- 把部署 workflow 和公开 URL 与本地构建分开检查。
删除示例 Book 或 Blog 之前,要同时移除对应顶部菜单根,以及首页上指向它的卡片。每整棵 删除一个表面就严格重建一次,才能让失败归因到单一改动。
下一步
用 Starter 仓库导览查询文件职责,再继续阅读 编写页面与配置总览。已有站点不应 继承 Starter 内容模型时,改走从零建站路径。
2 - Starter 仓库导览
本页说明从 pgsty/oink-starter
创建的仓库,不再介绍大得多的 oink.pgsty.com 文档与回归测试仓库。主题源码不会
复制进任何一个站点:go.mod 以 Hugo Module 形式固定版本,Hugo 把解析结果存进
Go 模块缓存。
顶层地图
oink-starter/
- oink-starter/
- hugo.yaml身份、语言、输出、参数与模块导入
- go.mod站点模块与精确 OINK 版本
- go.sum模块校验和
- examples/
- hugo.single.yaml仅英文的完整 profile
- hugo.bilingual.yaml英文 + 中文的完整 profile
- data/
- home/
- en.yaml每种语言一份精简落地页
- zh.yaml
- fr.yaml
- home/
- content/
- _index.md各语言首页根
- _index.zh.md
- _index.fr.md
- docs/简介、快速上手、教程、参考
- blog/文章、设计记录、发布说明
- book/介绍 Starter 的连续教程
- assets/
- icons/logo.svg经 Hugo 处理的项目 Logo
- static/
- favicon.svg原样复制到站点根
- i18n/
- fr.yamlStarter 自有法语界面覆盖
- .github/workflows/
- github-pages.yaml严格构建与 GitHub Pages 部署
- cloudflare-pages.yaml严格构建与 Cloudflare Direct Upload
- README.md面向仓库维护者的操作摘要
- LICENSE模板源码许可证
生成的 public/、resources/、.hugo_build.lock 与模块缓存是被忽略的构建状态,
不是源码。
最先修改什么
| 路径 | 职责 | 第一次操作 |
|---|---|---|
hugo.yaml |
身份、规范 URL、语言、输出、主题功能、可选集成 | 修改两个标记值;其它修改前先选择语言 profile |
data/home/ |
首页承诺、卡片与行动入口 | 一种语言确认后,再重写所有已启用语言 |
content/ |
全部读者可见内容 | 替换示例叶子;确认整个表面不要时才删除栏目根 |
assets/icons/logo.svg |
经处理的 Logo | 有正式图形后再替换 |
static/favicon.svg |
浏览器图标 | 与 Logo 一起评审后替换 |
hugo.yaml 中的 params.github_* |
编辑、历史、新建页面与 issue 链接 | 目标仓库已存在后才取消注释 |
哪些必须保留
go.mod与go.sum:两者共同固定并校验 OINK v1.0.0,都要提交。hugo.yaml中三项 Goldmark 设置:原生 Steps、Cards、Fields、图片属性与 Book 目标都依赖它们。outputs:删除markdown、LLMS或print,会有意删除对应的 Markdown、 Agent 索引或打印表面。- workflow 中的
fetch-depth: 0:保留enableGitInfo时,最后修改与贡献者事实需要 完整 Git 历史。 - CI 中的
GOWORK: off与HUGO_MODULE_WORKSPACE: off:开发者本地 workspace 不得 替换 CI 正在验证的公开版本。
可选表面
Docs、Blog 与 Book 是彼此独立的顶层表面。安全删除其中一个的顺序是:
- 删除对应的
content/<surface>/内容树; - 删除首页指向它的卡片或链接;
- 确认其它页面不再链接它;
- 严格构建,并检查剩余顶部导航。
不要只删除某种语言的栏目根:那会形成难以区分「有意不对称」与「漏译」的语言专属导航 和回退行为。要么在所有已启用语言中删除整个表面,要么明确记录这种不对称。
完成语言选择后,examples/ 下两个配置 profile 可以删除,也可以作为参考保留;真正
生效的站点配置只有根目录 hugo.yaml。
内容与导航
Docs 与 Book 下的目录结构和 weight 共同形成侧栏与翻页顺序。顶部导航来自栏目根的
menus.main。译文根重复相同的 identifier、parent 与 weight,只翻译可见标签。
Starter 刻意演示 Documentation System 内容模型:
- 简介回答是什么、为什么;
- 快速上手帮助新用户得到结果;
- 教程带领读者完成端到端任务;
- 参考记录精确的受支持行为。
可以按项目需要改名或重组,但应保留不同学习路径之间的分工,不要把所有答案混进一棵树。
语言模型
英文源码以 .md 结尾,中文和法语对页分别以 .zh.md、.fr.md 结尾。首页数据按
data/home/ 下的语言键分文件。根 profile 声明语言、locale、顺序与站点描述。
单语与双语 profile 仍声明被禁用的语言,这是有意设计:Hugo 会把未使用后缀识别为 译文,而不会把多个文件渲染到同一个英文 URL。只在项目配置开始前复制 profile;之后 应手工合并。
OINK 在哪里
两个文件建立模块边界:
hugo mod graph 显示实际解析版本。生产使用 go.mod 中的精确标签;本地
HUGO_MODULE_REPLACEMENTS 只是开发覆盖,绝不能提交,也不能当成发布证明。
部署文件
GitHub Pages workflow 在推送 main 后自动运行;仓库设置必须选择 GitHub Actions
作为 Pages Source。Cloudflare workflow 默认手动运行,只有仓库变量
CLOUDFLARE_PAGES_ENABLED=true 存在时才自动执行;所需账号 ID 与 API token 始终
保存在仓库 secrets 中。
只保留实际运营的部署路径。Cloudflare Direct Upload 与 Cloudflare Git integration 是同一个项目的两种所有权模型,不是应当同时运行的两道关卡。
安全的定制顺序
- 证明未修改的预览可用。
- 修改身份并选择语言。
- 替换一种首页,再补齐译文。
- 替换内容并验证导航。
- 品牌与阅读功能一次只改一组。
- 启用完整的外部集成。
- 执行严格生产构建。
- 部署,再独立验证生产环境。
仓库已经属于自己后,每层之间做一次提交。小边界能让后续回归与回滚明确归因到一个决定。
验证
模块图应显示固定发布,构建没有警告或错误,Git 状态只包含源码修改而没有 public/ 或
缓存。之后打开所有已启用语言的根,以及代表性的 Docs、Blog、Book 路由,再进入部署。
相关
- 使用 OINK Starter — 完整分层流程
- 从零建站 — 不采用这套内容模型,只接入 OINK
- 组织内容 — 侧栏、翻页与菜单权威
- 配置总览 — 当前全部站点参数
- 发布上线 — 托管商配置与生产检查
3 - 从零建站与其它安装方式
这是推荐路径 OINK Starter 的手工替代方案。本页从空目录
搭建一个最小 OINK 站点:一份精简 hugo.yml 加一条 hugo mod get,得到一个可预览
的单语站点。代价是首页、示例内容、部署 workflow 与每种组件用法都要自己组装。
已有 Hugo 站点时不需要脚手架:装上主题模块,再补三项 goldmark 前置配置(见写 hugo.yml),正文不用重写。已有 Docsy 站点见版本升级。
后半部分是四种安装方式的取舍:Hugo Module、Git submodule、离线归档、固定版本克隆。 当前 v1.0.0 发布路径应使用 Go 1.27 与 Hugo Extended 0.165.0;只有刻意维护旧环境的 既有站点才使用主题声明的较低兼容下限。
从空目录到第一页
-
建骨架并获取主题
hugo mod init后面跟的是你自己站点的模块路径,通常就是仓库地址。hugo mod get会写出go.mod与go.sum,两个都要提交。最新版本号在 GitHub Releases;本页出现的
v1.0.0是本站当前固定的版本。生产站点固定到发布标签,不要跟随main:@latest是一次性解析动作,不是版本策略。 -
写
hugo.yml把
hugo new site生成的hugo.yaml改名为hugo.yml(两个后缀 Hugo 都接受,本文统一用后者),内容替换为下面这份,可直接构建:hugo.yml五段分别管什么:
段 管什么 少了会怎样 顶层 + languages站名、域名、语言与顶栏菜单 baseURL不对,线上所有绝对链接指错markup.goldmark三项组件前置 属性行变成正文里的一行 {.steps}params搜索、仓库链接、外壳开关 交互功能默认关闭,主题不替站点决定 outputs每页的 .md、llms.txt、打印页页面菜单里没有「复制 Markdown」,也没有打印视图 module引用主题、声明 Hugo 下限 构建时找不到主题 -
写第一页
content/下的每个一级目录是一个分区,目录结构就是侧栏结构。文档分区至少要有一个_index.md:content/docs/_index.mdcontent/docs/install.md标题写显式
{#id}:后续加译文时两种语言的锚点才能对应。页面写法见编写页面。 -
预览
打开 http://localhost:1313/,侧栏里有 Docs → Install。修改文件是毫秒级热重载。
其它安装方式
上面用的是 Hugo Module。另外三种方式面向特定约束:网络隔离、平台要求构建输入包含完整主题树、组织内部需要评审主题副本。除 hugo mod vendor 之外,它们都不建立 Go 模块,站点用 theme: oink 而不是 module.imports 引用主题;共同的代价是版本解析与完整性校验由你自己负责。
Hugo Module(推荐)
唯一能让 Hugo 自己解析版本、校验 checksum、并在 go.sum 里留下审计记录的方式。hugo mod graph 看实际解析结果,hugo mod get -u 升级。需要本机有 Go。
Git submodule
在站点仓库里记录准确的主题 commit:
CI 必须在运行 Hugo 之前初始化 submodule,否则 themes/oink 是空目录:
离线归档
网络隔离环境使用。两条路径,都先在联网机器上准备,再整体搬入。
用 hugo mod vendor:把已解析的主题源码固化进站点目录,之后构建既不联网也不需要 Go。
_vendor/ 存在时 Hugo 优先使用它(hugo mod graph 输出 +vendor),hugo.yml 里的 module.imports 保持不变。这一步需要 Go,之后的构建不需要。升级主题要回到联网环境重新执行 hugo mod get 与 hugo mod vendor。
_vendor/ 只收主题挂载出来的目录(assets data i18n layouts static)以及 hugo.yaml 与 theme.toml,不含 LICENSE、NOTICE 与 VENDOR.json。要对外分发这份归档,把这三个文件从主题仓库一并取来。
用 tag 源码归档:不建 Go 模块,直接把某个版本的主题解压到 themes/oink/。
主题仓库的根目录就是模块根目录,解压出来直接是 layouts/、assets/、i18n/、static/ 这一层,不需要再进入下一级。重新分发时必须保留 LICENSE、NOTICE 与 VENDOR.json。最后一个记录了每个第三方运行时的版本、来源、许可证路径与 SHA-256,是离线审计的依据。
跨机器传输时,在联网侧从不可变标签生成归档与校验值:
把归档与 .sha256 一起传入隔离环境,先校验再解压:
这样得到的归档是自建产物,不是项目发行物。某个标签的发行页面是否附带归档与校验文件按发布而定,使用公开附件时独立验证其校验值。
断网构建之前确认归档内容完整,这十一项都要在:
themes/oink/
- oink/
- go.mod模块路径声明,Hugo Module 方式解析用
- hugo.yaml主题默认参数与 Hugo 版本下限
- theme.toml主题元数据,theme: oink 方式需要
- LICENSEApache-2.0
- NOTICE上游署名,再分发时必须保留
- VENDOR.json第三方运行时清单:版本、来源、许可证路径、SHA-256
- assets/SCSS、JS 与随主题分发的第三方运行时
- layouts/模板、partial、shortcode、render hook
- static/字体文件,原样发布
- i18n/32 份界面语言文件
- data/页尾出处行用的 SPDX 许可证表
固定版本克隆
托管平台要求构建输入包含完整主题树时用:
与 submodule 的区别是主题文件直接进入你的仓库历史,没有 .gitmodules 这层间接。记录最终解析出的 commit 与恢复流程。
四种方式对比
| 方式 | 需要 Go | 版本可审计 | 主题源码进你的仓库 | 适用 |
|---|---|---|---|---|
| Hugo Module | 是 | go.sum 自动校验 |
否 | 默认推荐 |
| Git submodule | 否 | 仓库记录 commit | 以引用形式 | 需要主题源码在库内 |
| 离线归档 | 否 | 手工核对 checksum | 是 | 网络隔离 |
| 固定版本克隆 | 否 | 需自行记录 | 是 | 平台要求完整树 |
Bootstrap、Font Awesome、字体、搜索与图表运行时全部随主题分发。站点不需要 node_modules、PostCSS、RTLCSS,也不需要 CDN。为 Docsy 站点安装 npm 依赖的教程属于上游 Docsy 的流程,不适用于 OINK。
用本地主题 checkout 开发
同时修改主题与站点时才需要这一节。把两个仓库克隆为同级目录:
用环境变量 HUGO_MODULE_REPLACEMENTS 把模块临时替换为本地 checkout,go.mod 不变:
文档站仓库的 Makefile 就是这几条命令的别名,make dev 与 make check 要求主题 checkout 在同级目录 ../oink:
Go workspace(go work init + HUGO_MODULE_WORKSPACE=go.work)是等价的另一种做法。两种做法都只作用于本机:CI 与生产构建用的是 go.mod 里的版本,go.work 不要提交。
验证
构建以 Total in … 结束、没有 WARN / ERROR 即通过。再确认:
/docs/打得开,侧栏里有你写的页面- 顶栏有搜索框,搜得到刚写的标题
- 深浅色切换按钮在,切换后代码块配色跟着变(说明
markup.highlight.noClasses: false生效) git status里有go.mod与go.sum,没有public/、resources/
相关
- 快速上手 — 在 Starter、既有 Hugo 站点与迁移之间选择
- OINK Starter — 推荐的新站点路径
- Starter 仓库导览 — 模板各目录的职责
- 配置总览 —
hugo.yml每个键的含义与默认值 - 编写页面 — 第一页之后怎么继续写
- 版本升级 — 升级主题模块、从 Docsy 迁移