博客减重记:从 8200 行代码到架构反思
周末整理博客代码,顺手跑了个统计:84 篇内容,8200 行 TypeScript,46 个运行时依赖。这个数字让我愣了一下——一个个人博客,怎么搞出了小型 SaaS 的体量?
从 PDF 功能开始动手
最早装 @react-pdf/renderer 的时候,想法很朴素:每篇文章应该有一个"导出 PDF"按钮,读者可以下载保存。为了这个按钮,我陆续加了:
lib/markdown-to-pdf.tsx(478 行),手写了每个 Markdown token 类型到 React-PDF 组件的映射lib/pdf-document.tsx(202 行),定义 A4 页面模板components/PdfButton.tsx,前端按钮组件app/api/pdf/[slug]/route.ts,动态 PDF 生成 APIpublic/fonts/下放了两个中文字体文件(合计 34MB)
六个文件,外加一个重量级依赖。而实际上,我从来没有在任何一篇文章的 frontmatter 里填过 pdfUrl。
这大概就是过度设计的典型症状:为了一个没人用的功能,搭了一整套管道。
于是第一个 commit 就是把这些全删了。git diff --stat 显示 +48 -1081,净减少一千多行。删代码的快感,懂的都懂。
更深层的问题:抽象与规模不匹配
PDF 只是冰山一角。继续审视代码,发现更隐蔽的问题。
lib/content/ 目录下有三个文件——module.ts、repository.ts、types.ts——用了工厂模式 + 仓储模式 + 缓存层 + 字段选择器,把"读目录下的 Markdown 文件"这件事封装成了一个泛型内容模块系统:
export function createContentModule<TIndex, TFull>(config) {
// buildIndex → createIndexGetter → createContentCache
// → createGetBySlug → createGetAll → selectFields → toFieldsKey
// 返回 { get, getFull, list, listFull, adjacent, tags, byTag, totalCount }
}看起来很漂亮。问题是,这个 450 行的基础设施只服务了 3 个内容类型——posts、essays、albums。每个类型本质上只需要 fs.readdirSync + matter() 两行。
类似的还有 Feed 渲染管道:feed.ts → mdx-render.tsx → feed-cache.ts,用 MDX 检测 + React prerender + SHA-256 缓存来为 RSS feed 生成 HTML 内容。而大多数博客直接用 marked 一行搞定。
工程中的"规模意识"
这件事让我想起一个老生常谈但总被忽略的原则:解决方案的复杂度应该与问题的规模成正比。
三个内容类型 → 不需要泛型工厂。八十篇 Markdown → 不需要分层仓储。个人博客的 RSS → 不需要 React prerender 管道。
我们写代码的时候,很容易陷入一种心态——"万一以后扩展呢"、"这样写更规范"——然后把企业级模式套在一个小项目上。但"万一"很少发生,"规范"的代价却是实实在在的维护负担。
不是说这些模式不好。工厂模式、仓储模式、缓存层——在几百个 API 端点、几十个数据源的项目里,它们是必需品。但在一个从文件系统读 Markdown 的个人博客里,它们只是噪音。
后续打算
PDF 删完只是第一步。接下来准备:
- 把
lib/content/的泛型工厂拆成直接函数 - 用自己写的轻量请求替换
@aws-sdk/client-s3(上传脚本里已经手动实现了 Signature V4,列文件无非一个 GET 请求) - Feed 渲染直接用
marked,砍掉 MDX→React→HTML 的转换链 - 清理那些给 AI Coding 工具配的实验性配置文件
目标是代码量降到 5000 行以内,依赖数降到 35 个以下。
这些数字本身不是目的,但它们是一面镜子:如果你的代码比你的内容还重,那一定是哪里搞错了。

