十几天前,我只是想给几个新项目找一套顺眼的文档主题。

十几天后,我把自己的 18 个网站全部迁了过去

从 Pigsty 的中英文文档、Silo、PIG、SOW、PG Exporter,到公司首页、个人博客,再到《设计数据密集型应用》这样的多语言书籍,现在全部运行在同一套框架上。

打开网易新闻 查看精彩图片

这可不是什么换个颜色、改个字体的小装修。十八个网站的内容结构、语言、规模和用途各不相同:有上千个页面的大型技术文档,有只有几页的小工具,有博客,有书籍,有下载站,还有纯粹用来做产品介绍的 Landing Page。

把它们全部迁过去,是一个相当折腾的工程。

但一个作者对工具最高级别的评价,不是 README 里写了多少卖点,也不是给自己点了多少个 Star,而是:敢不敢把自己的生产环境全部押上去。

现在我押了。所以,我觉得 Oink 终于可以正式发布了。

打开网易新闻 查看精彩图片
打开网易新闻 查看精彩图片
打开网易新闻 查看精彩图片
打开网易新闻 查看精彩图片
打开网易新闻 查看精彩图片
打开网易新闻 查看精彩图片
打开网易新闻 查看精彩图片
打开网易新闻 查看精彩图片

Oink 到底是什么?

Oink 是一个开箱即用、本地优先的 Markdown 文档框架。

你只需要三样东西:

Markdown

Hugo Extended 二进制

一个 Git 仓库。

然后就可以得到一套现代、完整、可搜索、可打印、支持深浅色、多语言、多版本,并且可以直接部署到 GitHub Pages、Cloudflare Pages、Nginx 或任何静态服务器上的网站,通过 Git 推送直接发布更新。

整个构建过程不需要 Node.js,不需要 npm install,不需要 PostCSS,不需要外部 CDN,也没有必须常驻的后端服务。主题所需的字体、样式、图标与交互运行时全部随项目本地交付,一条 hugo 命令就能生成完整的静态目录。断网也能构建与服务。

当然,你也可以完全让 Claude 与 Codex 来操心这些事。

打开网易新闻 查看精彩图片

它当然可以被称为一个 Hugo Theme,但我觉得这个说法有点低估它。我更愿意把 Oink 称为一套 文档发行版。普通主题解决的是“页面长什么样”,Oink 想解决的则是整套问题:

•内容应该怎么组织;•文档、博客和书籍如何共存;•搜索、导航、多语言和版本管理怎么做;•技术组件应该怎样书写;•同一份内容如何输出给浏览器、打印机和 AI Agent;•网站如何构建、检查、部署、升级和长期维护。

打开网易新闻 查看精彩图片

换句话说,它不是只给 Docsy 换了一张脸,而是把我这些年维护工程文档时踩过的坑、形成的判断和积累的组件,封装成了一条完整的交付路径。

Markdown in, Modern Docs out。

这就是 Oink 最核心的产品定义。

至于名字,也很简单:一边是文档与墨水的 Ink,另一边是 Pigsty 宇宙里小猪的叫声 Oink。README 里那句 Open, Indexed, Navigable, Knowledge,就当作附赠的彩蛋好了。

为什么偏偏是现在?

因为现在写代码实在是太快了。

以前做一个新开源项目,最费时间的通常是把功能做出来。现在有了 Codex、Claude Code 和各种 Agent,项目原型可能一个下午就冒出来了,仓库、工具、组件和小产品的数量正在迅速膨胀。

但项目做完之后,问题才刚刚开始:

README 要写,安装指南要写,配置参数要写,发布注记要写,API 文档要写,博客和产品首页最好也得有。否则代码虽然存在,别人却不知道它是什么、能解决什么问题、该怎么用,更不知道出了问题该去哪里找答案。

AI 大幅降低了生产代码的成本,却没有自动消灭文档。恰恰相反,它制造了更多需要被解释、被组织、被交付的东西。

文档正在从过去的附属品,变成项目交付链条里越来越明显的瓶颈。

打开网易新闻 查看精彩图片

另一方面,Markdown 也正在获得一种新的地位。

过去我们说 Markdown 是程序员写文档的通用格式;现在,它实际上已经成了人类与 AI Agent 之间的公共协议。README、AGENTS.md、Skills、任务说明、项目记忆、设计文档、知识库,越来越多的信息最终都落在 Markdown 里。

这是一个很重要的变化。

以前的文档框架,主要考虑的是怎样把内容渲染给浏览器里的人看。现在,一个真正现代的文档系统,还应该考虑:

•AI 能不能直接读;•内容能不能无损复制;•页面有没有干净的 Markdown 版本;•整个站点有没有机器可发现的索引;•源文件里是不是混进了大量只对某个前端框架有意义的噪音。

Oink 会为每个页面生成对应的 .md 版本,在站点根目录生成 llms.txt,并提供“复制为 Markdown”“查看 Markdown 源码”以及可选的“在 ChatGPT / Claude 中打开”入口。这些都是构建时生成的静态产物,不需要额外服务,也不会把正文偷偷上传到什么地方。

所以 Oink 并不是简单地“支持 AI”。

它的内容模型从一开始就在假设:这份文档既要给人看,也要给机器读。

我受够了现代文档框架

做 Oink 还有一个非常朴素的原因:我确实受够了现有的文档框架。

这些框架并不是不能用,其中不少甚至相当优秀。问题在于,它们往往要求你在一些我不愿意接受的东西之间做选择。

MDX:为了表现力,把内容变成私有方言

第一类是大量使用 MDX 的框架。

我理解 MDX 的吸引力。你可以在 Markdown 里直接插入 React 组件,想画什么就画什么,表现力几乎没有上限。

打开网易新闻 查看精彩图片

但它的代价也非常明确:内容和表现逻辑开始纠缠在一起。

文档里逐渐出现组件标签、属性、嵌套容器、导入语句和各种只有特定框架才能理解的东西。写到最后,它看上去不再像 Markdown,而像一份披着 Markdown 外衣的 JSX。

打开网易新闻 查看精彩图片

这会带来三个问题。

第一,人直接阅读源文件时,噪音太多。

第二,AI Agent 读取时,要浪费上下文理解大量与内容无关的表现细节。

第三,一旦你想换框架,真正被锁住的不是页面样式,而是内容本身。

我始终认为:样式控制不应该大面积侵入内容。

内容是长期资产,主题只是它某一阶段的衣服。为了今天一个漂亮的选项卡,把十年后仍然有价值的文字写成某个前端框架的私有 DSL,这笔账不划算。

Node.js:为了写文档,先养一套前端工程

第二类问题是现代前端工具链。

你只是想写几篇文档,结果先得到一个 package.json、两个锁文件、几百兆 node_modules,再配上 bundler、插件、主题包和一串版本约束。

哪怕网站最终只是一些静态页面,构建它的过程也越来越像在维护一个前端应用。

这当然不是 Node.js 的原罪。对于真正复杂的 Web 应用,这些工具有充分价值。但对文档来说,我认为它们经常是明显的过度设计。

文档本来应该是一个项目里:

•最容易构建的东西;•最容易搬走的东西;•最容易离线保存的东西;•最不应该因为依赖升级突然坏掉的东西。

结果现在恰好反了。

代码还能编译,文档站先因为某个插件、锁文件或者构建环境开始闹脾气。你只是想改个错别字,却先要研究为什么今天的构建和半年前不一样。

我不喜欢这种感觉。

轻量框架不够用,完整框架又不好看

还有一类像 Docsify 这样的轻量工具,确实简单,但当你需要大型导航、多语言、多版本、打印、SEO、复杂组件、书籍交叉引用和完整发布体系时,很快就会感觉表现力不足。

Docsy 则正好相反。它的内容模型和工程能力很成熟,毕竟是 Google 发起、CNCF 项目大量采用的一套方案,但它的默认界面确实已经很有年代感,而且很多实现仍然带着旧时代前端工程的包袱。

打开网易新闻 查看精彩图片

Hextra 和 Blowfish 我也都用过,而且挺喜欢。前者适合文档和书籍,后者适合博客,但当我同时维护十几个网站时,我不想再维护三套内容方言、三套配置方式和三套定制逻辑。

打开网易新闻 查看精彩图片
打开网易新闻 查看精彩图片

所以我的需求逐渐变得非常明确:

有没有一套统一框架,能够同时处理文档、博客、书籍、产品首页、发布下载和 API Reference,同时仍然坚持纯 Markdown、静态构建、本地优先和长期可维护?

没有完全符合我要求的。

那就自己做一个。

Oink 的答案:让内容保持干净,让框架承担复杂性

Oink 的设计并不是从一张设计稿开始的,而是从十几个真实网站里倒推出来的。

我先把这些网站实际需要的能力汇总起来,再尝试寻找一组尽可能简单、稳定和统一的抽象。

最终形成了几个非常明确的原则。

第一,原生 Markdown 优先

Oink 的第一原则不是“禁止组件”,而是:

能用原生 Markdown 表达的东西,就不要发明新的语法。

比如步骤列表,本质上还是一个普通的有序列表,只需要在后面加上一行属性:

打开网易新闻 查看精彩图片

即使离开 Oink,这仍然是一份人类和其他 Markdown 工具都能读懂的有序列表。

文件树也是一样。很多框架会要求你写一大堆嵌套组件,而 Oink 使用一个接近纯文本的围栏块:

打开网易新闻 查看精彩图片

在 Oink 里,它会变成带文件图标、对齐注释、折叠目录和可拖动分隔线的文件树;离开 Oink,它仍然是一段一眼就能看懂的文本。

Callout 使用普通引用块,步骤使用普通列表,字段说明使用普通表格,图片、代码块和数据围栏也尽量沿用 Markdown 已有的语义。只有当原生 Markdown 确实无法表达某种能力时,才退回到 shortcode。

这不是洁癖,而是一条非常现实的工程原则:

框架总会过时,内容应该活得比框架更久。

第二,只用 Hugo,别的都不要

Oink 使用 Hugo Extended 编译模板与样式。

正常构建不会从网络拉取字体、JavaScript 或第三方资源;浏览器侧需要的运行时也全部随主题一起交付。最终产物就是一个普通的 public/ 静态目录。

hugo --gc --minify

构建完了,事情也就结束了。

你可以把它扔给:

•GitHub Pages;•Cloudflare Pages;•Netlify;•Nginx;•Caddy;•对象存储;•内网服务器;•离线安装包;•任何能托管静态文件的地方。

没有数据库,没有应用服务器,没有必须在线的 SaaS,没有某家搜索服务的账号,也没有部署后还得继续养着的 Node Runtime。

打开网易新闻 查看精彩图片

这就是 Oink 所说的 Local-First

这里的“本地优先”并不只是“可以在 localhost 打开”,而是指整个系统的关键能力都掌握在你自己手里:

•内容在你的 Git 仓库里;•资源在你的构建产物里;•搜索索引在你的站点里;•构建过程可以复现;•网站可以离线运行;•托管平台随时可以更换。

文档尤其应该这样。

它不应该因为某个 CDN、外部字体服务、托管搜索或者前端依赖停止工作,就突然变成一堆打不开的壳。

第三,一套框架,六种内容

真实项目很少只有“文档”。

一个成熟项目通常还会慢慢长出:

•产品首页;•博客与技术文章;•发布注记;•安装包与下载页;•API Reference;•有章节、图表和交叉引用的长篇书籍。

传统做法是为这些东西分别选择工具,然后努力让它们看起来像一家人。

Oink 反过来:先提供一套统一的页面外壳、导航、搜索、主题和输出体系,再在上面承载六类内容。

文档

左侧目录树、右侧页内纲要、面包屑、上一篇/下一篇、编辑链接、历史链接、全文搜索和键盘导航都已经准备好。

打开网易新闻 查看精彩图片
打开网易新闻 查看精彩图片

博客

支持作者档案、多作者署名、系列文章、标签分类、RSS、分享栏、列表/卡片/表格索引,以及适合长文阅读的沉浸式 Hero 页面。

打开网易新闻 查看精彩图片
打开网易新闻 查看精彩图片

书籍

支持章节编号、图片与表格编号、公式、交叉引用、目录索引以及整本书连续打印。DDIA 这样的多语言复杂书籍,就是这套模型的主要试验场。

打开网易新闻 查看精彩图片
打开网易新闻 查看精彩图片

发布与下载

一份结构化的 YAML 数据,可以生成发布卡片、下载资产列表、校验和与历史归档。发布状态不再散落在手写 HTML 里,而是成为可以检查和复用的数据。

打开网易新闻 查看精彩图片

Landing Page

Oink 内置了一组服务端渲染的首页区块,可以用数据和 Markdown 组合产品介绍、功能板块、指标、价格、FAQ、团队与行动入口。

你现在看到的 Oink 首页,以及 PGSTY 公司首页,本身就是用这套 Landing 系统搭出来的。

打开网易新闻 查看精彩图片

API Reference

Swagger UI 和 Redoc 以本地运行时的方式交付。OpenAPI 文件放在仓库里,即使在断网和内网环境中也能照常查看。

这六类内容并不是六套互不相干的主题。它们共享同一套设计语言、搜索、语言切换、版本选择、输出格式和组件体系。Oink 当前同时提供 21 类技术内容组件,并且只在页面实际使用某项能力时加载对应运行时。

这件事看似只是“少配几套主题”,真正带来的价值却很大:

你终于可以用一套知识,维护整个项目的对外信息系统。

第四,功能可以很强,页面不能越来越重

Oink 里有不少高级组件:

打开网易新闻 查看精彩图片

但我的原则不是把所有 JavaScript 一股脑塞进每个页面里。

一个页面没有图表,就不应该加载 ECharts;没有 Mermaid,就不应该加载 Mermaid;没有终端录像,就不应该加载播放器。组件脚本根据当前页面实际使用的能力进行拼装,打印、Markdown 和 RSS 输出则完全不加载这些浏览器运行时。

这就是 Oink 所说的:

Content. On Demand. 能力按需出现,负担不要到处乱跑。

更重要的是,每个组件都应该知道自己离开浏览器之后是什么。

在 HTML 里,它可以是一棵漂亮的交互文件树;在打印页面里,它应该完整展开;在 Markdown 输出里,它应该保留原始围栏;在 RSS 里,它至少应该退化成可阅读的源码。

这种“优雅降级”不是锦上添花,而是 Oink 内容模型的一部分。

因为一个真正可靠的文档组件,不能只在作者当前使用的浏览器和主题版本里成立。

第五,为人类设计,也为 Agent 设计

Oink 对 AI Agent 的支持,不是在导航栏里随便加个 ChatGPT 图标就算完事。

它从输出层开始处理这个问题。

同一份 Markdown 内容,可以生成:

•HTML 页面;•打印页面;•原始 Markdown 页面;•RSS;•站点级 llms.txt 索引。

每个 HTML 页面都会声明自己对应的 Markdown 地址,Agent 和爬虫不需要先把导航、按钮、脚本与样式从 HTML 里剥掉,再猜哪一部分才是正文。

更关键的是,这个 Markdown 页面不是把 HTML 再反向转换一遍,而是尽可能保留你真正写下的内容。Callout、表格、文件树、代码围栏等组件也会按照各自定义的 Markdown 形态输出。

打开网易新闻 查看精彩图片

这意味着 Oink 站点不仅适合“被 AI 总结”,也适合成为 Agent 真正可操作的工程知识库。

你可以让 Agent:

•阅读站点结构;•找到特定配置;•获取当前页面的干净 Markdown;•根据已有文档继续补充内容;•检查中英文翻译;•更新发布注记;•直接在仓库里修改源文件。

在我看来,这是未来文档框架的基本能力,而不是一个可有可无的插件。

浏览器只是文档的一个消费者,AI Agent 正在成为另一个。

搜索、多语言与版本,不需要再从头拼装

这些功能很少成为文档框架宣传页上最性感的部分,但在真实项目里,它们决定了一套系统到底能不能用。

Oink 的全文搜索完全在本地运行:Hugo 在构建时为每种语言生成 JSON 索引,浏览器下载后在本地搜索,不需要爬虫、账号、外部 CDN 或托管服务。拉丁文字使用 Lunr,中文、日文等 CJK 查询则有子串回退机制,不会出现英文能搜、中文形同虚设的尴尬。

搜索入口与命令面板合并,通过 ⌘ KCtrl K 打开。除了搜文档,还可以搜索页面操作、语言切换与版本切换。

多语言直接使用 Hugo 原生模型,翻译文件与原文并排放置;英文、简体中文与繁体中文界面文本经过完整维护,页面之间可以建立稳定的语言对应关系。

多版本文档则提供版本菜单和旧版本归档提示,但不擅自规定你的部署方式。每个版本仍然是一个独立、可复现的 Hugo 构建,可以放在不同域名、子域名或者目录下。

除此之外,RSS、SEO、站点地图、Google Analytics、Giscus 评论、深浅色、打印样式、图片缩放、键盘导航、移动端布局和仓库编辑链接也都已经准备好了。

你当然可以自己把这些东西一点点拼起来。

但我做 Oink 的目的,正是让你不必再拼一次。

先别听我吹,直接看十五个生产站点

目前 Oink 的公开案例库收录了 15 个真实站点,覆盖中英文大型文档、公司首页、开源项目、小型工具、聚合知识库和三本书;规模从只有两页的小站,一直到拥有上千份内容文件的发行版手册。

如果你准备做一个新站,不需要从空目录开始研究所有配置。直接找一个最接近的现成站点,照着抄就行。

打开网易新闻 查看精彩图片

这些站点不是专门为了截图做出来的 Demo,而是我自己每天都在使用和维护的生产站点。

这点很重要。

Oink 的不少设计不是来自“我觉得用户可能需要”,而是来自真实迁移过程中遇到的具体问题。设计文档所依据的一轮调查,覆盖了 11 个消费站点和五千多份 Markdown 文件;公开案例又进一步覆盖了从小工具到大型发行版文档的两端。

所以它不是一套想象中的通用方案,而是许多真实需求最终收敛出来的公共部分。

它不是一张截图配一个 README

Hugo 主题很容易做出一个“看起来不错”的首页,然后在复杂内容、移动端、多语言、打印或者升级时原形毕露。

我不敢说 Oink 没有 Bug,但它不是一张截图配一个 README 的主题玩具。

0.6 版本除了真实站点验证,还覆盖了 HTML、打印、Markdown、RSS 与 LLMS 等 40 个黄金输出面,包含 85 项迁移测试、38 项浏览器运行时测试,以及双语站点构建、大型站点性能测量和真实中英文浏览器检查。

开发预览和生产发布也采用不同的错误策略。

普通 hugo server 遇到错误配置时,会尽量发出警告并安全降级,避免一个错别字让整个预览站点全部打不开;生产构建则使用 --panicOnWarning,让 CI 对警告保持严格,防止有问题的产物被发布出去。

这套思路很像数据库系统:

•开发阶段要尽可能提供诊断;•生产发布必须建立严格门槛;•错误不能悄悄制造误导性结果;•也不能因为一个局部输入问题,就让所有页面一起陪葬。

名字虽然叫 Oink,骨子里仍然是工程师做出来的东西。

怎么开始?其实不用先学 Oink

这年头,说实话,我写这么多文档也不全是指望人逐页阅读的。

最简单的使用方式,是把任务直接交给 Codex 或 Claude Code。

Oink 的文档站本身就是完整样例和回归测试站,包含所有页面类型与组件。官方推荐的最快路径不是从空目录开始,而是把样例站克隆下来,删掉不需要的部分,再替换成自己的内容。

对人类来说,命令只有几行:

git clone https://github.com/pgsty/oink.pgsty.com my-docs
cd my-docs
hugo server

然后修改站点名称、域名和仓库地址,把 content/ 换成自己的内容即可。

当然,你甚至不需要亲自做这些。

可以直接把下面这段话扔给 Agent:

打开网易新闻 查看精彩图片

你只需要负责两件事:

第一,告诉 Agent 你要做什么样的网站。

第二,把真正有价值的内容写出来。

剩下那些主题配置、目录组织、构建脚本和部署细节,本来就应该由工具处理,而不是让每个写文档的人重新学习一遍。

谁适合使用 Oink?

Oink 最适合下面几类人。

开源项目作者

项目已经写出来了,不想再花几天时间研究前端框架,只想尽快得到一套像样的首页、文档、博客和发布页。

基础设施与工程软件团队

数据库、运维平台、中间件、开发工具、发行版和私有化软件,往往需要大量配置说明、命令、架构图、文件树、终端录像和版本文档。Oink 的组件与本地优先交付模式,就是围绕这类内容设计的。

需要内网、离线或长期归档的团队

如果文档必须在私有网络、客户环境、离线安装包或隔离环境中运行,那么不依赖外部 CDN、搜索服务和运行时后端,会省掉很多麻烦。

写书、做翻译和维护大型知识库的人

章节编号、交叉引用、图表公式、多语言、整书打印和稳定锚点,都是长篇内容真正会遇到的问题。

同时维护多个项目的人

当你有五个、十个甚至更多项目时,统一框架的收益会迅速放大。你不再需要记住每个站点各自使用什么主题、什么组件方言、什么部署方式。

我自己把 18 个网站迁过去,就是最直接的例子。

谁不适合使用 Oink?

Oink 不是万能框架,也没有必要装成万能框架。

如果你需要的是:

•拖拽式所见即所得 CMS;•在线多人协作编辑后台;•复杂的用户账号与权限系统;•大量动态业务状态;•一个可以随意嵌入 React 应用的前端平台;•依赖服务端实时计算的 Web 应用;

那么 Oink 不是正确选择。

它的边界很清楚:以 Markdown 和结构化数据为源,构建静态、现代、可靠的内容网站。

这个边界不是缺陷,而是它能够保持简单的原因。

文档本来就应该是最不容易坏的东西

过去几年,我在文档这件事上确实折腾了不少。

用过 Docsy,拿 Hextra 放书,用 Blowfish 写博客,也试过各种前端框架。它们各有长处,但当网站越来越多、内容越来越复杂之后,我最终还是回到了最朴素的一条路:

•内容使用 Markdown;•构建使用单一二进制;•输出是普通静态文件;•所有资源尽可能本地交付;•样式和内容尽可能分离;•对人类和 Agent 使用同一份事实源。

这不是因为新技术不好,而是因为文档这种东西,真正重要的并不是技术栈有多时髦。

真正重要的是:

•十年之后还能不能打开;•换个平台还能不能部署;•离线之后还能不能使用;•人和机器还能不能读懂;•项目越来越大之后还能不能维护。

文档本来应该是一个项目里最容易部署、最不容易坏的东西。

Oink 只是试图把它变回本来该有的样子。

目前 Oink 已经以 Apache 2.0 协议开源,保留了 Docsy 的历史与上游署名,并对随主题分发的第三方运行时逐项记录许可信息。

它还远没有完工,但现在已经足够好,至少好到让我愿意把自己的十八个网站全部放在上面。

而只要这些网站还在,我就会继续维护它。

你不需要先研究一堆概念,也不需要从空白目录开始。

去案例库里找一个最像你需求的网站,复制下来,把内容换成自己的,然后交给 Agent 收拾剩余细节。

把 Markdown 喂进去。

剩下的,交给这头猪。

打开网易新闻 查看精彩图片

•Oink 文档与案例[1]•Oink GitHub 仓库[2]•Oink 文档站源码[3]

References

[1] Oink 文档与案例: https://oink.pgsty.com/
[2] Oink GitHub 仓库: https://github.com/pgsty/oink
[3] Oink 文档站源码: https://github.com/pgsty/oink.pgsty.com

数据库老司机

点一个关注 ⭐️,精彩不迷路

对 PostgreSQL, Pigsty,下云,AI 感兴趣的朋友

欢迎加入 PGSQL x Pigsty 交流群 QQ 619377403