7.2 万 Star 的开源 headless CMS,拆开 Strapi 的双协议与 AI 化底牌

发布于 2026年07月09日 12:56 #Github 解读 原文链接

7.2 万 Star 的开源 headless CMS,拆开 Strapi 的双协议与 AI 化底牌 封面图
  • Strapi 坐拥 72k Star、9.8k Fork、9 年维护史,在「开源 + 自托管」路线稳坐 headless CMS 头把交椅,非僵尸仓库
  • 四层请求流(Routes → Middlewares → Controllers → Services)中,v5 引入 Document Service 实现关注点分离,core-api 只翻译 HTTP 参数、不碰数据库
  • 安全隐患:REST 分页的 maxLimit 默认为 null(不设上限),生产环境若不手动配置 api.rest.maxLimit 可被单次查询拖垮数据库
  • 双协议精算:ee/ 目录外的代码走 MIT,企业功能(AI、审计日志、review-workflows)闭源收费,是 GitLab、Sentry 同款的 open-core 商业模式
  • 前瞻信号:MCP 集成放进社区版让 AI Agent 直接读写内容,但 Strapi AI 在 ee/ 下是企业版,CMS 正从「人填数据」转向「AI Agent 供数据」

72633 个 Star,9801 个 Fork,9 年维护史。这是 Strapi 交给 GitHub 社区的成绩单。

说真的,headless CMS 这个赛道不缺选手,WordPress 还活着,Contentful 和 Sanity 在 SaaS 那头吃得很饱,国产的语雀、飞书文档也在抢内容生产侧。但 Strapi 能在「开源 + 自托管」这条窄路上稳坐头把交椅,靠的不是又一个 CRUD 生成器那么简单。

我前两天翻它的源码,本来只想搞清楚一件事,就是「开源 CMS 到底能开源到什么程度」。结果挖出来的东西比预想的有意思得多,一个 MIT 协议的招牌下面,藏着一套精算过的双协议商业设计;一个「给前端供数据」的老定位,正在悄悄转向「给 AI Agent 供数据」的新叙事。

这篇就把这些拆开讲。

先说清楚 Strapi 到底解决什么

一句话,它让你在后台用可视化拖拽的方式定义内容模型(Content-Type),然后自动生成 REST 和 GraphQL 两套 API,前端、移动端、IoT 设备随便消费。

听起来不新鲜对吧。但它的价值密度藏在一个反直觉的设计选择里,就是你定义的每一个内容模型,不是一个数据库表的简单映射,而是一个完整的「文档」生命周期。

传统 CMS 给你的是一张表 + 一个后台表单。Strapi 给你的是一个 Document,带草稿、发布、国际化版本、内容发布计划、甚至 AI 自动填充。这个差异决定了它不是「数据库管理工具」,而是「内容运营平台」。

npx create-strapi@latest my-project 一把梭,SQLite 开箱即用,PostgreSQL/MySQL/MariaDB 按需切换。v5.50.1 就在昨天(7 月 8 日)刚发,这个项目非常活跃,不是那种 star 很高但两年没更新的僵尸仓库。

请求流拆开看,四层管道的设计哲学

README 里那张图把请求流画得很清楚,Routes → Middlewares → Controllers → Services。但光看箭头方向没用,得读源码才知道这四层是怎么咬合的。

读完整条链路后,我画了一张 Strapi 的分层架构图,把从协议接入到落库、再到 open-core 协议底牌的全貌放在一张图里,下面的拆解可以对照着看。

Strapi 分层架构
Strapi 分层架构

自上而下五层,核心是中间的 Document Service。上层(core-api)只翻译参数不碰数据库,下层(strapi.db)只管落库,Document Service 夹在中间独占「文档生命周期」这件事。右侧底部的 open-core 协议层是另一条暗线,ee/ 目录的边界就是商业边界。

核心请求流的实现在 packages/core/core/src/core-api/ 目录。这里有个很关键的设计决策,Controller 和 Service 都按内容类型分成了两套,collection-type.tssingle-type.ts。集合类型(文章列表、商品列表)和单例类型(站点配置、关于页)走的是完全分离的处理逻辑,不是靠 if-else 区分。

翻开 core-api/service/collection-type.ts,看它的 find 方法长什么样。

async find(params = {}) {
  const { uid } = this.contentType;
  const fetchParams = this.getFetchParams(params);
  const paginationInfo = getPaginationInfo(fetchParams);
  const isPaged = isPagedPagination(fetchParams.pagination);

  const results = await strapi.documents(uid).findMany({
    ...fetchParams,
    ...paginationInfo,
  });
  // ...
}

注意这里,Controller 层的 find 自己不碰数据库,它把活儿全委托给 strapi.documents(uid).findMany()。这个 strapi.documents(uid) 就是 Strapi v5 引入的 Document Service,是整个数据层的真正主人。

为什么这么设计?因为旧版 Strapi 的 Entity Service 把数据库查询、权限过滤、国际化、草稿逻辑揉在一起,耦合太重。v5 把这些拆开了,Document Service 专注「一份文档的完整生命周期」,core-api 这层只负责把 HTTP 参数翻译成 Document Service 能懂的语言。这是典型的关注点分离,读懂这个,你就懂了为什么 Strapi 敢说自己「fully customizable」,因为每一层都能单独替换。

分页这块也有讲究。pagination.ts 里同时支持两种模式,paged 模式(page + pageSize)和 offset 模式(start + limit),靠 has('page') 还是 has('start') 来判定走哪条路。默认配置在这两行。

const getLimitConfigDefaults = () => ({
  defaultLimit: toNumber(strapi.config.get('api.rest.defaultLimit', 25)),
  maxLimit: toNumber(strapi.config.get('api.rest.maxLimit')) || null,
});

默认每页 25 条,这个没什么问题。但 maxLimit 默认是 null,意思是「不设上限」。你想想看,如果前端直接把 pageSize=100000 传进来,没有 maxLimit 兜底,一次查询就能把数据库拖垮。这是个真实存在的隐患,生产环境必须手动在配置里设上 api.rest.maxLimit,否则就是敞着门让人打。

Strapi AI 这事,得把话说清楚

README 顶部大字宣传「Strapi AI,Automate content modeling, media alt text, and translations」。乍一看是社区版福利,对吧。

我去翻了源码,AI 服务的入口在 packages/core/admin/ee/admin/src/services/ai.ts

注意那个 ee/。在 Strapi 的代码组织里,凡是落在 ee/ 目录下的东西,都是企业版功能。这意味着 Strapi AI 不是社区版能白嫖的,它属于 Enterprise Edition,受另一套完全不同的协议约束(下面会讲)。

这个发现挺值得玩味的。README 把 AI 放在很显眼的位置当卖点,但普通开发者 create-strapi 装完社区版,是碰不到这个功能的。这不是骗人,因为 README 也写了「Learn more」链接,点进去能看到企业版页面。但信息呈现的方式,确实会让没细看的人产生误解。

坦白讲这种做法在开源商业项目里不算罕见,MongoDB、Elastic 都干过类似的事。但作为读者你得知道,Strapi 的「开源」和「全功能免费」是两码事。

MCP 集成,才是真正的前瞻性

如果说 AI 是商业化的噱头,那 MCP(Model Context Protocol)的集成才是我眼里 Strapi 真正有前瞻性的地方。

实现在 packages/core/content-manager/server/src/mcp/ 目录下。逻辑是这样的,Strapi 会根据你定义的每一个 Content-Type,自动派生出对应的 MCP 工具定义,然后注册到 MCP server 上,让 AI Agent 能直接读写你的内容数据。

核心注册函数在 register-content-manager-mcp-tools.ts

if (strapi.ai.mcp.isEnabled() !== true) {
  return;  // MCP 禁用时安全跳过,不做昂贵派生
}
const models = getService('content-types').findDisplayedContentTypes();
const tools = deriveDisplayedContentTypeMcpToolDefinitions(strapi, models, {
  localeCodes, defaultLocale,
});
for (const tool of tools) {
  strapi.ai.mcp.registerTool(tool);
}

注意这段代码的注释,「Performance only,registerTool() is safe when MCP is disabled」。意思是即使 MCP 关着,注册函数本身也不会出错,只是不执行派生逻辑。这是个很克制的工程实现,没有为了功能完整性强行加载,而是在入口就短路。

而且关键的是,MCP 这块代码不在 ee/ 目录,是社区版能用的。也就是说,即使你不买企业版,也能让 Claude、Cursor 这些 AI Agent 通过 MCP 直接操作你的 Strapi 内容。

这才是真正的趋势信号。CMS 的定位正在从「人填数据的后台」变成「AI Agent 调用的数据后端」。当 Agent 时代真的来了,内容管理的入口可能就不再是 admin 面板,而是 AI Agent 的一次工具调用。Strapi 提前把 MCP 这条路铺好了。

MIT 的招牌,双协议的精算

现在说最关键的一块,协议。

GitHub 页面上 Strapi 的协议显示是 Other,SPDX 标识是 NOASSERTION。这两个词放在一起,对懂行的人就是个预警信号,说明这不是一个标准的单协议开源项目。

打开根目录的 LICENSE 文件,内容分两段讲得明明白白。

第一段是社区版,ee/ 目录之外的代码,MIT Expat 协议,爱怎么用怎么用,改、卖、分发都行。

第二段是企业版,只要你的代码路径走到了 ee/ 目录下,就不再适用 MIT,而是受 Strapi Enterprise Software License Agreement 约束。这个协议的原文是这样的。

BY ACCESSING OR USING THE ENTERPRISE EDITION OF THE STRAPI SOFTWARE, YOU ARE AGREEING TO BE BOUND BY THE RELEVANT REFERENCED AGREEMENT.

坦白讲,企业版只有两条合规路径,要么签书面协议,要么订阅 Strapi Cloud,二选一,没有「我拿去商用但不付钱」这个选项。

这是一种叫「open-core」的商业模式,核心开源,高级功能闭源收费。GitLab、Sentry 都是这条路。它的精妙之处在于,用 MIT 的招牌吸引开发者社区(攒 Star、攒贡献者、攒生态),再用企业功能变现(SSO、审计日志、AI、review workflows 这些都在 ee/ 下)。

对个人开发者来说,社区版够用,协议没风险。但如果你是技术负责人,想在公司内部深度使用,就得想清楚哪些功能是必须付费的。我把企业版的功能扫了一遍,audit-logs(审计日志)、Strapi AI、review-workflows(内容审核流)这些企业刚需都在 ee/ 目录下。绕不开的。

issue 区的真实痛点

源码之外,issue 区是另一个信息矿。我翻了高赞未关闭的 issue,挑几个有代表性的说说。

#20870「Failed to fetch dynamically imported module」,55 条评论。这是个困扰不少用户的前端动态导入失败问题,通常出在 admin 面板构建后的资源加载上,和部署环境的路径配置有关。评论区里各种 workaround,但官方一直没给根治方案。

#23161「Drag & Drop broken in Configure View」,44 条评论。内容管理后台的拖拽功能在某些场景下坏了,这种交互层的 bug 对内容编辑体验是硬伤。

#22946「Strapi 5 local plugins,size has significantly increased」,26 条评论。v5 升级后本地插件的体积明显变大,这对打包构建性能有实际影响。

#26738 是个依赖更新的 issue,bump undici 从 6.x 到 7.28.0。看着不起眼,但说明底层 HTTP 客户端的版本迁移也在进行中,Strapi 团队没有躺平吃老本。

这些 issue 拼在一起,能看出一个真实的 Strapi,它很活跃,迭代很快,但 admin 前端的稳定性和 v5 大版本的迁移成本是当前主要痛点。对一个 7 万 Star 的项目,这种规模的问题不算致命,但选型时得心里有数。

什么时候该用 Strapi

收尾不堆溢美之词,给点能落地的判断。

Strapi 适合你的场景,如果你需要给多个前端(Web、App、小程序)统一供内容,内容模型会频繁变化且不想每次都改后端代码,团队需要非技术人员能自主管理内容,或者你想让 AI Agent 通过 MCP 直接操作结构化内容。

Strapi 不适合你的场景,如果你的内容模型很简单(就一两张表),用不上可视化的 Content-Type Builder 和权限体系,那直接用框架自带的 ORM 更轻。如果你对 admin 前端的稳定性有极高要求,受不了偶尔的交互 bug,那 Strapi 的前端还达不到企业级 SaaS 的成熟度。如果你需要审计日志、SSO、内容审核流这些功能又不想付费,那社区版的天花板会很快顶到。

最后说一个我读完源码的判断。headless CMS 这个品类,最大的变化不是功能变多,而是它的「消费者」正在从人类编辑扩展到 AI Agent。Strapi 把 MCP 做进了社区版,把 AI 塞进了企业版,这个分层是有意识的,前者圈生态,后者赚利润。

一个开源项目能同时把开源社区的口碑和企业变现的闸门都握在手里,靠的不是技术多炫,而是协议设计的精算。这种「用开放换规模,用闭源换利润」的平衡术,值得每个想做开源商业化的人琢磨。

评论互动

© 2026 王若风的技术博客 · Powered by Astro