一、先说背景
前两年待过一个小团队,后端六个人,内部服务十四个,对外 API 三个。文档在 Confluence 上,命名大概是「订单API说明-v3-最终版-别删」这种。每个季度都有人问「这个字段到底是不是必填」,然后大家去翻代码。
后来我们把三个 API 全迁到了 OpenAPI,用 Swagger UI 渲染。迁移那天挺爽的,两周后没人再打开过那份 yaml。原因很简单:写代码和改 spec 是两件事,而人只有两只手。
真正让 spec 活过来的,不是我换了哪个渲染器,是我在 CI 里塞了三行命令。这个是后面第五节的重点,前面都是铺垫。
二、选型时大家盯的地方,基本都不重要
我见过太多对比文章在比主题色、暗黑模式、侧边栏折叠动画。这些在 demo 里确实好看,但你团队里真正的用户是「刚接手的实习生」和「半夜十二点排查线上问题的你自己」,他们不关心动画。
我只用三个问题筛工具:
- 这个 spec 能不能被 lint?规则能不能自己定义,而不是只能开它内置那几条。
- 文档里的 example 能不能直接拿去起 mock server?不能的话,example 就是写给领导看的装饰品。
- 每次改 OpenAPI 文件,CI 能不能告诉我「你删了一个响应字段,下游有三个调用方在依赖它」?
三个都是否,那这工具再漂亮,也只是个静态页面生成器。
三、几个工具的实际差别(不是从官网抄的那种)
先拆一个被搞混的事:Swagger UI、Redoc、Scalar 是 API reference 渲染器;Mintlify、Docusaurus、Starlight 是站点框架。前者解决「怎么把一个 yaml 渲染成人能看的页面」,后者解决「整个文档站怎么搭、路由怎么分、版本怎么管」。拿 Mintlify 和 Swagger UI 比,约等于拿 Next.js 和 React 比。
| 工具 | 定位 | 我遇到的好 | 我遇到的坑 |
|---|---|---|---|
| Swagger UI | 渲染器 | 谁都会用,插件多,swagger-ui-dist 直接丢 CDN 就行 |
端点上百之后首屏明显卡;搜索基本等于没有;对 OpenAPI 3.1 的支持长期滞后,webhooks 那块渲染不出来 |
| Redoc | 渲染器 | 三栏布局,左侧导航右侧多语言样例,redocly build-docs openapi.yaml -o docs.html 一条命令出单文件,扔 nginx 就完事 |
社区版的 Try it 能力弱,改样式要跟它的主题变量较劲 |
| Scalar | 渲染器 | 默认样式确实顺眼,代码样例能一键切 curl / fetch / Python,不用自己写模板 | 生态薄,schema 写得野一点(oneOf 套 allOf 再套 $ref)会直接渲染成空白 |
| Starlight | 站点框架 | Astro 生态,MDX 随便写,SEO 和构建速度都不错 | API reference 得自己接,它不管这事 |
| Mintlify | 站点 + 托管 | 开箱即用,写 MDX 就行,搜索和 AI 问答是现成的 | 托管方案,要花钱,私有化部署得单独谈 |
| Docusaurus | 站点框架 | 插件生态成熟,docusaurus-plugin-openapi-docs 能直接生成 API 页 |
构建慢,我这边一个三百来页的站要跑四分多钟 |
数字我不给太死。这类工具版本迭代快,我上次认真量 bundle 是去年底的事,Scalar 的入口体积比 Swagger UI 小一大截,具体差几倍你现在自己 npx 一把就知道。别信任何文章里的数字,包括我这篇。
四、OpenAPI 3.0 升 3.1 这件事,值得单独说
很多团队卡在 3.0 不敢升,主要怕工具不认。列几个我实际踩过的差异:
nullable: true在 3.1 里没了,要写成type: ["string", "null"]。升级脚本好写,但如果你有几十个 schema 手工改会崩溃。example(单数)和examples(复数)在 3.0 里是 OpenAPI 自己的关键字;3.1 全面对齐 JSON Schema 2020-12 之后,examples变成了数组,语义变了。我见过一个团队升级后 Swagger UI 里样例全部消失,就是因为这个。exclusiveMinimum在 3.0 里是布尔值配合minimum用,3.1 里直接是个数字。这坑很隐蔽,lint 不报,只有 mock 出来的边界值不对才会被发现。- 3.1 新增了顶层
webhooks字段,可以描述回调。但支持它的渲染器到 2024 年还是少数。
我的建议:新项目直接上 3.1,老项目先别动,除非你有明确的、非升不可的理由(比如要用 webhooks 描述回调)。升级动作本身十分钟,改工具链和排查渲染问题可能两周。
五、真正让文档活下来的三道闸
回到开头那句。渲染器决定「好不好看」,CI 决定「活不活」。我在 .github/workflows 里加了三个东西。
第一道,Spectral。
npx @stoplight/spectral-cli lint openapi.yaml --ruleset .spectral.yaml
内置规则先跑 spectral:oas,然后我自己加了几条。.spectral.yaml 长这样:
extends: ["spectral:oas"]
rules:
operation-operationId: error
oas3-valid-schema-example: error
另外还加了一条自定义规则:每个 4xx 响应必须带 examples。以及每个 description 不许超过 120 个字符——是的,文档写太长没人看,这条规则被同事吐槽过,但半年后没人提了。
第二道,用 example 起 mock,让测试打上去。
npx @stoplight/prism-cli mock openapi.yaml -p 4010
合约测试跑一遍。这一步能抓出「schema 写的是 string,example 里给的是整数」这种低级错误,而这恰恰是接口文档里出现频率最高的错误。
第三道,breaking change 检测。
oasdiff breaking base.yaml head.yaml --fail-on ERR
删字段、改类型、把可选改必填,都会报出来。这条拦下来的事故,比我们任何一次 code review 都多。
六、文档里最值钱的部分,通常没人认真写
一个 API 文档,八成篇幅在列端点、参数、返回值。但真正降低支持成本的,是下面这三块,而且它们经常是空的:
- 错误码表。 不是「400 请求错误」这种废话。是「40001 参数缺失」「40002 手机号格式非法」这类业务码,加上「遇到之后调用方应该怎么办」。
- 可直接复制的 curl。 要带脱敏后的真实 token 占位符、真实的时间戳格式、真实的分页参数。粘上去就能跑通那种。
- 限流、分页、幂等、重试。 这四个单独开一节写,别散在参数说明里。分页要说清楚:
limit最大多少、超了会怎样、游标稳不稳定。
我的粗略统计是,接入方提的问题里有一大半能靠这三块解决。剩下那部分,才是文档解决不了、必须打电话的那种。
七、我最后留了什么
内部:Redoc 单文件 + Spectral + oasdiff,扔在一个 nginx 里,改完 spec 跑 CI 自动部署。
对外:Starlight 搭站点,API reference 用 Scalar 的组件嵌进 MDX,普通指南手写 Markdown。
为什么不统一成一个?因为这两类文档的读者根本不是一批人。内部那批人只想知道参数和错误码,越快越好,别跟他讲品牌调性;外部那批人要看 quickstart、要 SDK、要在手机上看。
工具是按读者分的,不是按团队分的。这一点想明白,选型就没那么纠结了。