这个博客平台如何运作:当前架构的实用导览
本文介绍该平台如何通过在 apps/admin、apps/www 和 @repo/ui 之间分离架构定义、公开交付与可复用展示层来保持可维护性,并说明生成的 Payload 类型、Markdown 优先的文章模式和 TurboRepo 工作流如何让内容、前端代码与本地开发保持一致。
概览
这个仓库是一个用于个人网站和技术博客的 monorepo,围绕一个简单的理念构建:
apps/admin是内容架构与后台行为的权威来源apps/www是面向公众的交付层packages/ui是可复用的展示层packages/typescript-config是共享契约层
这种分层乍看之下很常见,但真正有意思的是:各个部分如何在快速演进的同时保持一致。
- CMS 定义内容的形状
- 生成的类型让前端可以使用这一形状
- 公开网站通过一层轻量服务直接从 Payload 获取数据
- 文章现在以 Markdown 为优先,并在后台提供自定义字段,支持预览、浏览媒体和行内上传
本文从四个角度介绍这套架构:
- 工作区级别的模块依赖
- 公开请求如何得到响应
- CMS 内部的内容编辑流程
- 本地开发如何被统一编排
Monorepo 边界是第一个架构决策
从最高层看,仓库被拆分为三个应用和若干共享包:
apps/admin:Next.js + Payload CMSapps/www:公开的 Next.js 网站apps/storybook:组件沙盒packages/ui:共享 UI 组件、Markdown 渲染器与设计原语packages/typescript-config:共享tsconfig预设与生成的 Payload 类型packages/tailwind-config:共享样式配置packages/eslint-config:共享 lint 配置
关键在于,公开网站不会直接进入 CMS 内部。它通过 Payload API 消费内容,只共享稳定的契约和展示原语。
模块依赖图
这种布局带来了三个实际好处:
- 把应用专属行为留在各自应用中,避免
packages/变成杂物堆 - 通过
@repo/ui将视觉一致性变成共享关注点 - 通过生成的 Payload 类型,让数据契约保持明确
最重要的契约位于 apps/admin 与 apps/www 之间
在这个项目中,CMS 架构不只是实现细节,它也是公开网站的事实来源。
它们之间的关系如下:
很多全栈项目会把这层关系留在隐含状态,而这里将它明确表达出来:
apps/admin定义架构- Payload 生成 TypeScript 契约
apps/www在服务与路由组件中使用该契约@repo/ui专注于展示,而不负责数据获取
由此得到了一套清晰的职责划分:
- 架构逻辑位于 CMS
- 网络逻辑位于
payloadClient - 实体专属逻辑位于
services/payload - 渲染逻辑位于页面组件与
@repo/ui
运行时拓扑:公开流量不会直接访问 MongoDB
公开网站不会直接连接 MongoDB,而是通过 HTTP 与 Payload 通信。
这是一个有意设置的边界。它让前端保持简单,同时让 Payload 继续负责:
- 访问控制
- 草稿与已发布内容的筛选
- 关系解析
- 媒体 URL 生成
- 架构级 Hook
公开请求时序
当前的 posts 流程是一个很好的例子,因为它覆盖了主要层次:
apps/www路由组件services/payload/posts.tsutils/payloadClient.tsapps/admin的 Payload API- 用于文档的 MongoDB
- 用于媒体的 Vercel Blob 或 Payload 文件 URL
这里有几个重要细节:
apps/www优先使用 Server Components- 它不会再绕到
www内部额外的/api路由 - 服务层会添加默认缓存和重新验证行为
- UI 包负责渲染最终数据形状,但不拥有数据访问职责
文章以 Markdown 为优先,但创作流程仍由 CMS 管理
最近有一项架构调整尤其值得说明:
Posts.content不再使用 Payload 富文本- 它现在是自定义 Markdown 字段
- 该字段支持编辑与预览模式
- 可以浏览已有媒体
- 可以在编辑过程中上传新媒体
- 会直接插入 Markdown 图片语法:

这是一个很好的例子:根据真实写作流程调整创作模型,而不是强迫所有内容都进入通用富文本抽象。
CMS 编辑与发布时序
这种方式带来了几项架构收益:
- 写作体验更接近开发者熟悉的 Markdown 工作流
- 媒体仍然是 CMS 中的一等资源
- 预览保持在本地完成,成本低
- 持久化内容是纯 Markdown,而不是笨重的编辑器 JSON 树
它也带来一个明确的取舍:
- 与 Lexical 相比,结构化编辑能力更轻
- 但
posts更适合技术写作、代码片段和行内媒体
媒体被拆分存储为元数据与二进制文件
媒体并非存储在同一个地方。
- 元数据与关系通过
mediacollection 存放在 MongoDB 中 - 二进制文件通过 Vercel Blob 存储适配器保存
- Payload 负责将两者连接成可用的媒体 URL
这种拆分值得理解,因为它同时出现在运行时与创作流程中:
- CMS 与 Payload 通信
- Payload 把文件存入 Blob
- Payload 把文档元数据存入 MongoDB
- 前端只需要最终 URL
这样,公开网站就不需要包含任何存储实现专属逻辑。
本地开发经过统一编排,而不是临时拼装
仓库不要求开发者手动按正确顺序启动所有服务。
实际流程是:
- 根脚本调用 TurboRepo
- Turbo 统一协调开发、构建、lint 与类型检查任务
@repo/ui可以运行自己的样式与 TypeScript 输出监听流程admin与www作为独立的 Next.js 应用运行
本地开发调用流程
这里还有一项细微但重要的开发体验改进:
- 应用级
tsconfig路径在开发期间会把@repo/ui解析到packages/ui/src - 发布后的 package exports 仍然指向构建产物
- 这样既能获得更好的编辑器反馈与源码跳转,又不会改变生产环境中的包语义
为什么这套架构适合本项目
这套架构并不追求最大程度的通用性,而是追求明确和可维护。
其中最有价值的决策是:
CMS 优先的架构所有权
apps/admin是内容结构的唯一事实来源。API 优先的公开消费方式
apps/www通过 Payload 消费内容,而不是直接访问数据库。共享 UI,应用专属逻辑
展示进入@repo/ui,数据获取与组合则留在应用代码中。以生成类型作为契约边界
架构变更会成为前端可安全处理的变更,而不是只能依赖口口相传的知识。Markdown 优先的文章模式
长篇技术写作针对真实创作流程进行了优化。
取舍与需要重点关注的地方
任何架构都不是免费的。
这套方案换来了清晰度,但仍有一些取舍需要管理:
两个 Next.js 应用意味着两个部署单元
分离是有益的,但环境管理必须保持严谨。公开内容依赖 Payload 的可用性
apps/www通过 HTTP 与 Payload 通信,因此更简单;但这也意味着后台侧 API 必须稳定且可访问。生成的契约必须保持同步
架构变更后,pnpm gen不是可选步骤。Markdown 比富文本更轻,而不是更强
它更适合技术内容,但并不自动适合所有 CMS 管理的页面。
系统的真实形态
如果要用一句话概括这套架构,那就是:
CMS 负责结构,公开网站负责交付,共享包负责复用,而生成的类型让三者保持一致。
这正是它实用的原因:
- 编辑者拥有独立的后台应用
- 读者获得专注的公开应用
- 开发者获得可复用组件与明确契约
- 内容从架构到最终页面,始终沿着可预测的路径流动
对博客平台而言,这通常是恰到好处的复杂度:清晰可见、边界明确,而且容易推理。