Chankay Blog
演示文章
English简体中文
演示文章
Chankay Blog
GitHubBilibili

© 2026 Chankay Blog

这个博客平台如何运作:当前架构的实用导览

2026年3月19日•阅读 6 分钟

本文介绍该平台如何通过在 apps/admin、apps/www 和 @repo/ui 之间分离架构定义、公开交付与可复用展示层来保持可维护性,并说明生成的 Payload 类型、Markdown 优先的文章模式和 TurboRepo 工作流如何让内容、前端代码与本地开发保持一致。

概览

这个仓库是一个用于个人网站和技术博客的 monorepo,围绕一个简单的理念构建:

  • apps/admin 是内容架构与后台行为的权威来源
  • apps/www 是面向公众的交付层
  • packages/ui 是可复用的展示层
  • packages/typescript-config 是共享契约层

这种分层乍看之下很常见,但真正有意思的是:各个部分如何在快速演进的同时保持一致。

  • CMS 定义内容的形状
  • 生成的类型让前端可以使用这一形状
  • 公开网站通过一层轻量服务直接从 Payload 获取数据
  • 文章现在以 Markdown 为优先,并在后台提供自定义字段,支持预览、浏览媒体和行内上传

本文从四个角度介绍这套架构:

  1. 工作区级别的模块依赖
  2. 公开请求如何得到响应
  3. CMS 内部的内容编辑流程
  4. 本地开发如何被统一编排

Monorepo 边界是第一个架构决策

从最高层看,仓库被拆分为三个应用和若干共享包:

  • apps/admin:Next.js + Payload CMS
  • apps/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.ts
  • utils/payloadClient.ts
  • apps/admin 的 Payload API
  • 用于文档的 MongoDB
  • 用于媒体的 Vercel Blob 或 Payload 文件 URL

这里有几个重要细节:

  • apps/www 优先使用 Server Components
  • 它不会再绕到 www 内部额外的 /api 路由
  • 服务层会添加默认缓存和重新验证行为
  • UI 包负责渲染最终数据形状,但不拥有数据访问职责

文章以 Markdown 为优先,但创作流程仍由 CMS 管理

最近有一项架构调整尤其值得说明:

  • Posts.content 不再使用 Payload 富文本
  • 它现在是自定义 Markdown 字段
  • 该字段支持编辑与预览模式
  • 可以浏览已有媒体
  • 可以在编辑过程中上传新媒体
  • 会直接插入 Markdown 图片语法:![alt](url)

这是一个很好的例子:根据真实写作流程调整创作模型,而不是强迫所有内容都进入通用富文本抽象。

CMS 编辑与发布时序

这种方式带来了几项架构收益:

  • 写作体验更接近开发者熟悉的 Markdown 工作流
  • 媒体仍然是 CMS 中的一等资源
  • 预览保持在本地完成,成本低
  • 持久化内容是纯 Markdown,而不是笨重的编辑器 JSON 树

它也带来一个明确的取舍:

  • 与 Lexical 相比,结构化编辑能力更轻
  • 但 posts 更适合技术写作、代码片段和行内媒体

媒体被拆分存储为元数据与二进制文件

媒体并非存储在同一个地方。

  • 元数据与关系通过 media collection 存放在 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 负责结构,公开网站负责交付,共享包负责复用,而生成的类型让三者保持一致。

这正是它实用的原因:

  • 编辑者拥有独立的后台应用
  • 读者获得专注的公开应用
  • 开发者获得可复用组件与明确契约
  • 内容从架构到最终页面,始终沿着可预测的路径流动

对博客平台而言,这通常是恰到好处的复杂度:清晰可见、边界明确,而且容易推理。

On this page

  • 概览
  • Monorepo 边界是第一个架构决策
  • 模块依赖图
  • 最重要的契约位于 apps/admin 与 apps/www 之间
  • 运行时拓扑:公开流量不会直接访问 MongoDB
  • 公开请求时序
  • 文章以 Markdown 为优先,但创作流程仍由 CMS 管理
  • CMS 编辑与发布时序
  • 媒体被拆分存储为元数据与二进制文件
  • 本地开发经过统一编排,而不是临时拼装
  • 本地开发调用流程
  • 为什么这套架构适合本项目
  • 取舍与需要重点关注的地方
  • 系统的真实形态