做技术文档站点的同学几乎都经历过这样一个阶段文档从一个仓库开始还行等产品线多了、版本多了、团队多了就会发现“文档工具选型”这件事比想象中麻烦十倍。我最早用 GitBook后来切到 VuePress中间也试过自己拼一套基于 Sphinx 的流水线直到最后在 Antora 上停了下来。如果你正在为多仓库、多版本的技术文档发愁这篇从一个实际使用者角度写的长文应该能帮你省下大量试错成本。Antora 是一个以“多仓库、多版本”为核心设计目标的静态文档站点生成器底层依赖 Node.js 和 Git内容格式采用 Asciidoc。它和 VuePress、Docusaurus 这类“单仓库友好”的生成器定位完全不同。Antora 更擅长处理的是那种“文档本来就散落在好几个 Git 仓库、每个仓库还要维护多个历史版本、发布时想一键全部聚合”的场景。下面我把这套东西从原理、配置到实战中踩过的坑完整拆开讲一遍。1. 为什么偏偏是 Antora文档工具选型背后的现实考量1.1 多仓库与多版本Antora 解决的真正痛点大部分团队刚开始写文档时都是把所有.md文件放进一个仓库配一个静态站点生成器完事。但产品一复杂第一个崩掉的就是这套“单仓库单版本”模型。你在一个企业级云平台团队里负责文档上游网关、SDK、CLI、管理控制台各有各的仓库每个仓库还有维护中的 1.0 和 2.0 两代版本站点首页需要把这么多内容统一展示出来还要求用户能自由切换组件和版本。传统做法是写脚本把人家的文档仓库拷进来、复制目录、改路由每次版本发布都像在拆雷。Antora 从设计上就把这个场景当成了默认情形。它使用一个叫antora-playbook.yml的“剧本”文件描述整个站点要聚合哪些内容源构建时动态从各个 Git 仓库拉取指定分支依据每个组件仓库里的antora.yml识别组件名称和版本最后组合成一颗完整的内容资源树渲染成静态站点。源仓库不需要为站点做任何目录适配发布也只需要改一下剧本文件里的分支列表。它把“内容的分散管理”和“站点的统一呈现”解耦得干干净净。1.2 与 VuePress / Docusaurus / GitBook 的取舍对比很多人一开始疑惑为什么放着生态丰富、模板好看的 VuePress 不用非要选一个在国内社区讨论度不那么高的 Antora我把几类工具的定位和适用场景做了个对比就清楚多了。工具核心定位多仓库支持多版本支持技术栈主要短板GitBook团队知识库、书籍出版弱弱Node.js新版转向商业化老版依赖维护成本高格式绑定较重VuePress / Docusaurus单站点产品文档、官网弱中需插件Vue / React多仓库多版本需自行设计插件和目录约定长期维护成本高Docsify轻量在线文档弱弱纯前端运行时渲染SEO 偏弱适合内部轻量场景Antora复杂多产品技术文档站点强原生强原生Node.js Asciidoc需要学习 Asciidoc 语法和内容模型初期上手指引不如前端社区丰富选型时不妨问自己三个问题站点是否需要同时聚合多个 Git 仓库的内容是否需要为一个组件同时维护多个已发布版本文档内容复杂度高不高是否有代码示例、跨文件引用、文本替换等强需求如果三个里有至少两个答案是“是”Antora 就是那条更省力的路。尤其是第三个问题Antora 选择 Asciidoc 而不是 Markdown很多人以为只是作者偏好实际上这是真正的分水岭。Asciidoc 原生支持跨文件引用、文件片段包含、条件属性、变量替换这些在大型多人协作文档组织下比 Markdown 严格得多也安静得多。2. 核心概念一次吃透组件、版本与资源树2.1 组件Component文档的“最小独立单元”Antora 里最核心的概念是组件Component。你可以把它理解成“一个产品单元内文档的打包”。比如你做开发者平台有 API 网关、SDK、控制台三个子产品每个都能拆成一个组件。每个组件是一个独立的 Git 仓库或者至少是仓库里一个独立的目录由独立团队维护在站点里拥有独立的导航空间和 URL 路径。组件的身份靠仓库根目录下的antora.yml文件来定义字段包括name、title、version、start_page、nav等。最终渲染出来的 URL 长这样/组件名/版本号/页面名。例如网关组件的 2.1 版首页就是/gateway/2.1/index.html。这里有一个重要经验组件粒度不要拍脑袋划。既不能太粗比如把整个平台所有产品文档塞进一个组件那样团队之间的提交就互相踩脚也不能太细比如把“如何配置日志”和“如何配置告警”分别拆成组件那导航和版本管理会碎片化到没法维护。合理的粒度差不多是“一个团队负责的一个可独立发布的产品单元”。我见过不少团队前期偷懒不拆分后期文档规模上来后重构迁移成本远高于一开始拆好的成本。2.2 版本Version分支加标签的文档版本化Antora 对版本的管理非常直白直接从你的 Git 分支名或标签名提取版本号。也就是说组件仓库里有一个1.0.x分支Antora 构建时就会把该分支的内容解析为该组件的1.0.x版本内容。如果你的分支名带前缀比如release-1.0则需要在antora.yml里用version字段把它映射成干净的版本号展示名称。版本之间不是平级的Antora 会按照语义化版本规则自动排序并在站点右上角提供版本切换菜单。默认展示的版本由site.start_page或组件内start_page决定没有明确指定的情况下Antora 选择版本号最高的那个。实战中有个常见需求已经发布过的旧版本要长期显示同时新的“未发布”主分支也要在站点上可见给团队内部预览。这时候主分支版本可以写成version: masterAntora 会把它标成“未发布”状态正式发布的版本用2.1、2.0、1.0之类的分支名即可。我建议这个环节尽早统一分支命名规范不然等仓库里积累了几十个分支光是清理版本映射就会让人崩溃。2.3 资源Content Source与资源树Antora 的构建心脏一个 Antora 站点聚合多个内容源Content Source每个源代表一个 Git 仓库。写剧本时通过content.sources数组列举所有仓库地址、需要拉取的分支列表以及组件内容在仓库内的起始路径start_path。Antora 构建时会把这些内容源全部做一次克隆然后按照“组件-版本-模块-页面/图片/附件/导航”的层级组织成内部资源树。资源树里常见的四类资源是pages文档页面主体通常是.adoc文件images页面引用的图片按模块组织attachments需要随文档分发的附件nav导航定义文件。这套模型理解清楚之后很多配置错误就都能自己排查了。比如页面图片失效多半是图片没有放在对应模块的images目录下导航失败是因为nav.adoc里声明的路径和实际页面路径对不上。资源树不是玄学它是 Antora 一套统一的内部约定所有组件、版本、模块都要遵守。3. 从零搭建Antora 项目结构与 playbook 配置实操3.1 环境准备与项目初始化Antora 本身是一个 Node.js CLI 工具使用前需要 Node.js 16 以上版本以及一台装有 Git 的机器。开始前建议先建一个专门的“站点仓库”它和内容仓库完全独立里面只放剧本、UI 配置和构建脚本。安装依赖很简单我用的是本地安装方式mkdir my-docs-site cd my-docs-site npm init -y npm install antora/cli antora/site-generator-default这里特别提醒尽量不要用全局安装npm i -g antora/cli。原因是不同项目可能依赖不同版本的 Antora本地安装可以把版本锁定在package.json里换机器、跑 CI 时不容易出现“我本地能构建服务器上构建报错”的诡异情况。初始化之后的目录结构大致是这样my-docs-site/ antora-playbook.yml package.json node_modules/ build/build/目录下面会有 Antora 运行时生成的 UI 缓存、临时内容快照以及最终输出的site/静态站点目录。3.2 详解 antora-playbook.yml站点构建的总剧本antora-playbook.yml是 Antora 的配置中枢。整个工具的使用体验好不好一半取决于剧本写得好不好。下面贴一份我实际项目的精简版逐段讲。site: title: 示例产品文档中心 url: https://docs.example.com start_page: gateway::index.adoc content: sources: - url: https://github.com/example/gateway-docs.git branches: [HEAD, 1.0] start_path: docs - url: https://github.com/example/sdk-docs.git branches: [HEAD, 1.1, 2.0] start_paths: [docs, docs-alt] ui: bundle: url: https://gitlab.com/antora/antora-ui-default/-/jobs/artifacts/HEAD/raw/build/ui-bundle.zip snapshot: true output_dir: build/ui output: clean: true dir: build/site asciidoc: attributes: idprefix: idseparator: - extensions: - asciidoctor-kroki runtime: fetch: truesite段落定义站点标题、最终部署地址和默认首页。start_page: gateway::index.adoc的意思是站点首页默认跳转到gateway组件的index.adoc页面。这个写法本身也体现了 Antora 跨组件引用的基本格式组件名::页面路径。content.sources是剧本里最重要的部分。每个源是一个 Git 仓库branches可以写成分支名数组其中HEAD是“仓库默认分支”的占位符1.0就表示拉取1.0分支。start_path或start_paths用于指定组件内容在仓库中的实际目录。比如网关仓库的文档代码放在docs文件夹下如果组件根目录就是仓库根目录这个字段也可以省略。ui.bundle指向 UI 主题包默认用官方的antora-ui-default即可。它是编译好的 zip 包Antora 构建时下载后解压到output_dir指定的目录。想定制样式时就需要 clone 官方 UI 源码改完执行gulp build之后产出自己的 bundle 包。output.clean: true会在每次构建前清空输出目录避免旧文件残留导致线上出现已删除页面的问题。asciidoc.attributes里我设置了idprefix: 和idseparator: -这是为了让页面自动生成的 HTML 锚点格式更干净。至于asciidoctor-kroki这是渲染马赛克图Kroki 图表的扩展如果你文档里不需要嵌入复杂图表可以不加。runtime.fetch: true表示每次构建都强制重新拉取远端仓库内容。这个选项适合发布场景日常本地开发时建议关掉直接使用缓存速度会快很多。3.3 antora.yml 组件描述文件怎么填每个组件仓库需要在内容起始路径下放一个antora.yml。它描述的是“这个仓库打包出来的组件长什么样”。我的网关组件配置文件一般长这样name: gateway title: API 网关 version: 2.1 start_page: index.adoc nav: - modules/ROOT/nav.adoc asciidoc: attributes: product-name: API 网关 product-version: 2.1 page-products: 网关、SDK、控制台name是组件的唯一标识它不仅影响 URL也是跨组件引用时的寻址依据。title是站点导航里显示的人类可读名称构建后如果看到页面标题和产品名对不上多半是这个字段没写。start_page指向组件的默认首页相对于内容源根路径。nav是导航文件的路径列表Antora 会按声明顺序组装导航菜单。这里需要补充一个关键细节组件内容在仓库里通常不是平铺的而是按模块module组织。一个标准模块目录是这样的gateway-docs/ antora.yml modules/ ROOT/ pages/ index.adoc install.adoc nav.adoc concepts/ pages/ forwarding.adoc nav.adoc ...ROOT是默认模块存储组件的核心页面和一级导航。concepts是自定义模块它有自己的页面目录和导航文件。模块这样划分的目的是让巨型文档按照“概念、操作、参考”等维度拆开每个模块有自己的导航文件代码片段复用和维护都更清晰。实际填写antora.yml时最容易犯的错误是start_page路径写错。注意它写的是相对内容源根目录的路径比如modules/ROOT/pages/index.adoc但通常在start_page里写index.adoc就够了因为 Antora 默认去ROOT模块里找该页面。如果你自定义了模块那么这里应该写成类似concepts:index.adoc的格式具体规则和跨组件引用完全一致。4. 进阶玩法多版本、跨组件引用与本地预览4.1 多版本文档的发布策略Antora 对多版本的处理是把“版本”和“Git 分支/标签”绑定所以发布策略的本质是 Git 分支策略。比较稳妥且身边团队用得最多的一套方案是长期维护的主开发分支main或master对应“最新未发布版本”每个正式发布版本维护一个长期分支例如1.0.x、2.1.x发布新版本时从主开发分支拉出新的长期分支紧急修复直接在对应版本的长期分支上进行构建时只把相应分支引入构建。剧本里branches字段可以同时配置多个分支写法上支持简单字符串、通配符也支持数组。例如content: sources: - url: https://github.com/example/gateway-docs.git branches: - HEAD - 1.0.x - 2.{0,1}.x这个配置会把主分支、1.0.x分支以及所有2.0.x、2.1.x的分支都拉进来。Antora 会根据分支名自动生成版本列表并在站点上展示版本切换菜单。注意通配符2.{0,1}.x这种语法是 Antora 自带的 glob 匹配规则不是标准正则用起来很顺手但也容易误匹配建议配合tags: false之类的选项把标签拉取关掉防止标签名也被当成额外版本引入。矩阵复杂后site.start_page和组件级start_page的一致性非常重要。否则用户访问站点根路径可能落到一个旧的默认版本产生“怎么首页不是最新文档”的疑问。4.2 跨组件引用怎么写文档站点的价值很多体现在内链的丰富程度。Antora 里写跨组件页面跳转语法非常直观参考 xref:sdk:install.adoc[安装 SDK] 的说明完成安装。xref是 Asciidoc 的交叉引用标签。sdk:install.adoc表示目标组件名为sdk目标页面是install.adoc。如果目标页面在当前组件内直接写xref:install.adoc[]即可。带锚点的跳转写法是xref:sdk:install.adoc#step-3[第三步]。跨组件引用中有一个坑同页面不同版本之间的跳转。默认情况下xref:sdk:install.adoc会引用与当前页面版本号相同的 SDK 组件版本。如果目标组件没有对应版本引用就会断掉。这种情况建议使用 Antora 提供的“版本软连接”功能在目标组件antora.yml里声明version.selector或使用xref:sdk2.1:install.adoc[]这种带版本限定符的写法。我在团队里推行的一个原则是跨组件引用尽量不写死版本号除非确实有强依赖否则版本升级时链接断掉的情况几乎避免不了。Asciidoc 还提供include指令这是 Antora 之所以能处理大型文档的又一核心原因。你可以把公共的代码块、版权声明、API 摘要做成单独的片段文件在多个页面里动态包含进来include::component-b:partial$common-api.adoc[]这里的partial是模块的一种特殊资源类型专门用于存放可复用片段。把经常变化的公共内容从页面中摘出去之后跨团队维护的冲突率会大幅下降。4.3 本地开发预览与 UI 调整Antora 构建的是静态站点本地预览不需要额外起一个开发服务器直接把构建产物扔给任意静态服务器即可。不过日常开发时我会针对“本地预览”和“正式发布”分别维护两份剧本本地剧本local-playbook.yml会把内容源地址指向本地相对路径content: sources: - url: ../gateway-docs branches: HEAD start_path: docs运行命令npx antora --pull local-playbook.yml如果你用的是本地相对路径--pull不是必须的加不加都能读取当前工作区内容这一点对日常写文档非常友好。正式发布时改用npx antora --pull antora-playbook.yml这里的区别是正式构建会强制重新从远端拉取仓库确保线上内容是干净、可复现的版本。UI 定制方面如果你不想用官方默认主题可以 cloneantora-ui-default仓库修改样式变量和页面模板后构建出自己的 bundle zip 包再替换剧本里的ui.bundle.url。定制 UI 的时间成本主要集中在模板学习和页面结构理解上对于只想改 logo、改主题色的小团队来说其实没必要走全套官方 UI 的变量体系已经覆盖了大部分定制需求。5. 踩坑实录Antora 实战中的典型问题与排查方法5.1 常见问题速查表这里把我自己以及身边团队在 Antora 使用过程中遇到的高频问题整理成一个速查表方便对照排查。症状可能原因解决办法构建成功但访问具体页面 404组件仓库缺少antora.yml或start_page路径配置错误在仓库内容起始目录下补齐antora.yml确认start_page相对于内容源根目录的路径正确所有页面标题显示异常或显示英文antora.yml中title字段缺失给组件补上title同时检查site.title是否配置版本切换列表里版本顺序不对分支名不符合语义化版本规范统一分支名例如1.0.x、2.1.x避免出现latest、v1这种会被解析成预发布版本的名称页面里的图片全部裂开图片没放在模块的images目录下确认图片位于modules/module/images页面里用image::图片名.png[]引用跨组件链接跳转到不存在的页面目标组件没有当前组件同名的版本使用带版本限定符的引用或确认目标组件该版本已加入构建构建非常慢或卡在拉取仓库阶段缓存目录过大、网络较差清理~/.cache/antora缓存本地调试时改用本地路径源中文内容构建后出现编码问题源文件编码不是 UTF-8统一保存为 UTF-8 无 BOM 编码并在 Git 配置中尽量避免自动转换换行符旧页面已经删除但站点上还能访问output.clean未开启在剧本output段设置clean: true5.2 我的几点独家心得最后说几个常规文档里不会写但实际用下来让我大受帮助的经验。第一内容仓库和站点仓库一定要分开。内容仓库让文档团队专注写作站点仓库放构建配置、CI 脚本、UI 定制资源。把两者混在一个仓库里内容和构建配置互相污染多人协作时冲突几乎无法避免。分开后内容仓库只要关注 Antora 目录约定即可站点发布频率完全独立。第二组件划分要克制不要为了抽象而抽象。Antora 虽然原生支持多组件但每个组件在站点上都有独立的版本切换和导航空间。组件太多用户找文档的成本就变高。我建议新项目从两三个组件起步等文档规模确实大到需要拆时再拆。早期拆得过细之后想合并的成本也很高。第三版本分支命名最好在第一天就定下规范。Antora 会从分支名识别版本你后面新建版本分支时如果不按照既有规范版本列表顺序就会被搞乱。我们团队现在的约定是main是未发布主分支1.0.x、1.1.x、2.0.x这类语义化分支是已发布版本拉新版本时严格从前一个发布分支创建。第四善用 Antora 的缓存机制。正式构建前跑一次npx antora --fetch antora-playbook.yml把内容拉全后续npx antora antora-playbook.yml不加--pull构建就会直接走缓存速度会快很多。这个技巧在 CI 上尤其有用能省下大量无谓的网络请求时间。第五把 Asciidoc 的复用能力用到极致。Antora 选 Asciidoc 不是没有理由的我在大型文档组织中才真正体会到好处公共代码块、变量、特性矩阵全部通过include和属性集中管理改一处全局生效。如果你还用 Markdown 思维写 Asciidoc等于浪费了这套工具一半以上的能力。从我个人的体验来说从 GitBook 迁移到 Antora 之后团队文档维护最大的变化不是构建速度而是“发布文档”这件事的心态变了。以前发版前要人工核对哪个仓库哪个分支更新了、哪个版本页面还在不在现在只需要改一行剧本里的分支列表构建完就是一套干净、多版本、全部内容对齐的站点。文档基建带来的这种确定感是很多花哨的编辑器功能替代不了的。如果你也在反复折腾文档工具建议给 Antora 一个机会花半天时间把官方文档里的内容模型读一遍再拿一个真实的组件仓库跑通链路你大概率能理解我为什么愿意把它写进团队的基础设施。