静态站点最大的优点是**没有服务器**:没有进程要守护,没有数据库要备份,没有依赖要升级,一个月不碰它也不会坏。
但只要你想加一个「登录后才能用的后台」,这个优点立刻变成障碍:
**登录的本质是服务端信任一个凭证。没有服务端,就没有地方建立信任。**
你可以把密码写死在 JS 里——任何人打开控制台都能看到。你也可以用 HTTP Basic Auth——那是托管平台的功能,且无法细粒度控制。你还可以……基本上没有别的办法了。
这就是 Git-based CMS 要解决的问题。
传统 CMS 的保存是 `UPDATE posts SET content = ... WHERE id = ...`。
Git-based CMS 的保存是 `git commit`。
这个重新定义是整个架构的支点。它一旦成立,三个难题同时消失:
| 传统 CMS 需要 | Git-based CMS 用什么替代 |
|---|---|
| 数据库存内容 | 仓库里的 Markdown 文件 |
| 服务器渲染页面 | 构建时静态生成 |
| 后台服务器 + 会话管理 | 静态页 + GitHub OAuth |
**内容不再存在数据库里,而是存在版本控制系统里。** 你顺带得到了:完整的修改历史、每一篇文章的 diff、随时回滚、分支上写草稿。
```
① 你在 /admin/ 写文章,点保存
│
▼
② CMS(一个纯静态的 SPA)调用 GitHub API
│ 带着你的 access token
▼
③ GitHub 在仓库里创建一个 commit
│ src/posts/2026-09-16-xxx.md
▼
④ 托管平台检测到仓库有新提交
│ 触发构建
▼
⑤ 构建器(Eleventy / Hugo / Astro)渲染出静态 HTML
│
▼
⑥ 新版本部署上线
```
全流程没有任何一个常驻服务进程。
这里有个容易被忽略的细节。上面的第 ② 步需要一个 access token,而拿 token 的流程是这样的:
```
浏览器 ──① 跳转──▶ GitHub 授权页
浏览器 ◀─② 回调─── GitHub(带一个 code)
│
│ ③ 用 code + client_secret 换 token
▼
??? ← 这一步必须有服务端
```
第 ③ 步**必须携带 `client_secret`**。而 `client_secret` 一旦放进浏览器,就等于公开——任何人都能拿它冒充你的应用。
所以哪怕整个架构都是静态的,**你仍然需要一小块服务端代码**,哪怕只是转发一次请求。
这就是为什么 Cloudflare Pages 的 `functions/` 目录、Netlify 的 Functions、Vercel 的 Edge Functions 会成为标配。它们不是「后端」,只是**几个用来完成 OAuth 握手的无状态函数**:
```js
// functions/callback.js —— 全部服务端代码就是这些
export async function onRequestGet({ request, env }) {
const code = new URL(request.url).searchParams.get('code');
const res = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({
client_id: env.GITHUB_CLIENT_ID,
client_secret: env.GITHUB_CLIENT_SECRET, // ← 只存在于这里
code,
}),
});
const { access_token } = await res.json();
// 把 token 通过 postMessage 交给同源的后台页面
return new Response(callbackPage({ token: access_token, provider: 'github' }), {
headers: { 'Content-Type': 'text/html; charset=utf-8' },
});
}
```
整段代码约 20 行,无状态,不存任何东西。
这套架构不是免费的。以下是它的真实代价:
**① 内容在仓库里是明文**
公开仓库意味着:**你写了但没发布的草稿,别人也能在仓库里看到。** 大多数 Git-based CMS 的「草稿」只是在构建时跳过,文件本身照样被提交。
要真正保密,只能用私有仓库,或者把草稿存在别处。
**② 没有即时预览**
传统 CMS 保存后刷新就能看到效果。这里必须等一次构建——通常 1~2 分钟。文章的预览通过本地 `npm run dev` 解决,但这要求你会用命令行。
**③ 没有实时协作**
两个人同时编辑同一篇文章,会撞车。对个人博客无所谓,对团队是硬伤。
**④ 每篇文章都是「一次部署」**
改一个错别字也会触发全站重建。站点大、构建慢的时候,这个摩擦会积累。
**⑤ 搜索、评论、动态查询都得另想办法**
没有数据库,就没有 `WHERE` 子句。搜索要用构建时生成索引的方案(如 Pagefind),评论要挂第三方(如基于 GitHub Discussions 的 Giscus)。架构上说得通,但确实是额外工作。
| 方案 | 适合 | 不适合 |
|---|---|---|
| **Git-based CMS** | 个人博客、技术文档、重视版本与可迁移性 | 团队协作、需要即时预览、内容要保密 |
| **Headless CMS**
(Contentful / Sanity) | 内容规模大、要对接多端、要精细的编辑权限 | 不想依赖第三方、想完全掌控数据 |
| **WordPress** | 需要插件生态、非技术编辑者多 | 不想维护服务器和数据库 |
| **纯手写 Markdown** | 只要几十篇文章、不在意可视化编辑 | 写作与发布流程想一体化 |
我的判断标准很简单:
**如果「内容」和「代码」本来就在同一个仓库里,Git-based CMS 是顺理成章的。
如果内容需要独立于代码演进,那它就不该住在 Git 里。**
这套架构最迷人的地方,是它**没有为了「后台」而引入一整个运行时**。
你付出的是一小块无状态的 OAuth 转发代码,换回来的是:没有数据库、没有服务器进程、完整的版本历史、以及随时能把整个站点复制到任何静态托管平台的能力。
在「够用」和「简单」之间,它划出的那条线,我觉得挺漂亮。