{"ok":true,"data":{"slug":"git-as-cms","title":"Git 即 CMS 把博客内容变成 Markdown 文件","summary":"不装数据库，不写后台。文章就是仓库里的一个 .md 文件，提交即发布，回滚就是 git revert。","date":"2026-10-06","category":"技术笔记","tags":["内容管理","Markdown","工作流"],"cover":"/images/covers/cover-workflow.svg","featured":true,"draft":false,"body":"\n我曾经为了写一篇两百字的随笔，需要做这些事：启动 MySQL、启动 Spring Boot、启动前端、打开后台、粘贴内容、点发布。\n\n现在我只需要做一件事：在 `content/posts/` 里新建一个 `.md` 文件。\n\n## frontmatter 就是元信息\n\n```md\n---\ntitle: Git 即 CMS 把博客内容变成 Markdown 文件\nsummary: 不装数据库，不写后台。文章就是仓库里的一个 .md 文件。\ndate: 2026-10-06\ntags: 内容管理, Markdown, 工作流\ncategory: 技术笔记\ncover: /images/covers/cover-workflow.svg\nfeatured: true\ndraft: false\n---\n\n正文从这里开始……\n```\n\n字段约定：\n\n| 字段 | 必填 | 说明 |\n|---|---|---|\n| `title` | 是 | 文章标题，同时用于 SEO 的 `<title>` |\n| `summary` | 是 | 摘要，列表页与 OG description 共用 |\n| `date` | 是 | `yyyy-mm-dd`，倒序排列的依据 |\n| `tags` | 否 | 逗号分隔，用于标签聚合页 |\n| `category` | 是 | 一级分类，对应旧站的「专栏」 |\n| `cover` | 否 | 封面图，建议 16:9 |\n| `featured` | 否 | `true` 时进首页精选区 |\n| `draft` | 否 | `true` 时仅开发环境可见，生产构建自动剔除 |\n\n## 构建期读取，而不是运行时请求\n\n```ts\n// src/lib/posts.ts\nconst modules = import.meta.glob(\"../../content/posts/*.md\", {\n  query: \"?raw\",\n  import: \"default\",\n  eager: true,\n}) as Record<string, string>;\n```\n\n`eager: true` 意味着这些文件在**打包时**就被读进内存。运行时的博客不会发出任何网络请求，也就不存在接口挂掉的风险。\n\n## 顺手解决的三件事\n\n### 1. 草稿保护\n\n```ts\n.filter((p) => !p.draft || import.meta.env.DEV)\n```\n\n写了没想好？设 `draft: true`，本地能看，线上不出现。不用再手动清理。\n\n### 2. 阅读时长\n\n中文按 400 字/分钟、英文按 200 词/分钟估算，比一律按 300 字符/分钟更贴近中文读者：\n\n```ts\nconst cjk = (body.match(/[\\u4e00-\\u9fff\\u3400-\\u4dbf]/g) || []).length;\nconst en = (body.replace(/[\\u4e00-\\u9fff\\u3400-\\u4dbf]/g, \" \")\n  .match(/[a-zA-Z0-9]+/g) || []).length;\nconst minutes = Math.max(1, Math.round(cjk / 400 + en / 200));\n```\n\n### 3. 目录（TOC）\n\n文章页右侧目录不是运行时扫描 DOM 的，而是构建期从正文里正则提取 `##` / `###`：\n\n```ts\nconst re = /^(#{2,3})\\s+(.+)$/gm;\n```\n\n这样目录和正文标题天然一致，不会出现「点目录跳不到」的情况。\n\n## 发布流程\n\n```bash\n# 新建文章 → 写内容\nvim content/posts/new-post.md\n\n# 本地预览\nnpm run dev\n\n# 提交即上线\ngit add content/posts/new-post.md\ngit commit -m \"post: 新文章标题\"\ngit push\n```\n\n托管平台监听 push，自动执行 `npm run build`，几十秒后文章就出现在线上了。整个过程没有数据库迁移、没有缓存刷新、没有重启服务。\n\n> 个人博客的内容量，几十年后也就几百篇 Markdown。文件是最耐久的数据格式。\n\n## 一点取舍\n\n这套方案的代价是**没有评论、没有浏览量、没有全文搜索的服务端能力**。所以评论我打算用 Giscus（数据留在 GitHub Discussions），浏览量用轻量服务或干脆不做。\n\n如果哪天我需要一个真正的后台，说明这个博客已经不是个人博客了——到那时再换架构也不迟。\n"}}