这套系统的逻辑
为什么文件就足够了?Quiet Archive 的核心设计决策与底层逻辑。
搭建这套系统的过程中,很多决策其实是关于「不做什么」的。但每一个「不做」的背后,都对应着一个具体的技术选择。
为什么不用数据库
数据库本身没问题。但对个人知识档案来说,它带来的负担往往比收益更大:
- 迁移成本高——数据格式是数据库自己的,不是你的。换一个数据库,或者服务器到期了,你得 dump、转换、再导入,每次都是一次冒险
- 备份不直观——你需要额外的定时 dump 流程,而不是简单的文件复制。很多人的博客数据就是这么丢的——不是服务器挂了,而是忘了备份
- 全文搜索受限——要么依赖外部服务(Elasticsearch、Algolia),要么自己在数据库里建索引,但中文分词又是另一个坑
- 对 Agent 不友好——AI Agent 不能直接读你的 MySQL,但它能读 JSON 和 Markdown
Quiet Archive 的做法很朴素:Markdown 文件 + Git 仓库。文件是你自己的,结构是透明的,备份就是 git push,历史就是 commit log。服务器到期了也不怕——把仓库 clone 到新机器就行,不存在数据迁移。
构建时索引:以空间换时间
既然不用数据库,内容查询怎么做?
答案是在构建时把所有计算做完。每次执行 npm run build,Python 构建脚本会扫描整个 posts/ 目录,生成一组 JSON 索引文件:
content-index.json— 所有内容条目的结构化元数据(标题、日期、标签、URI 等),是整个前端数据层的核心数据源search-index.json— 全文搜索索引,使用 jieba 做中文分词,支持标题、标签、摘要、正文的加权搜索route-map.json— URI 到文件路径的映射,用于动态路由解析archive-tree.json— 完整的目录树结构,反映posts/下的层级关系
运行时不做任何计算——前端页面通过 src/api/* 里的函数读取这些预生成的索引,纯静态文件查询。这意味着整个站点可以部署到任何静态托管服务上,不需要 Node 运行时,不需要服务器端逻辑。
之所以选 Python 而不是用 Node 脚本来做构建时索引,主要是因为 jieba(中文分词库)在 Python 生态里更成熟、更稳定。中文全文搜索如果不做分词,搜索质量会很差。
目录结构即内容结构
这是整个系统里我最喜欢的设计之一:
posts/
├── article/ ← 一个内容类型
│ ├── README.md ← 类型说明页
│ └── my-post.md ← 类型下的文章
└── work/ ← 另一个内容类型
├── project/ ← 一个项目目录
│ ├── README.md ← 目录说明页
│ └── notes.md ← 目录下的文章
└── README.md ← 类型说明页
你在文件系统里怎么组织,网站的 URL 和导航就是什么样——不需要写配置文件,不需要声明 sidebar,不需要手动定义路由。文件夹放在哪里,页面就在哪里。
这个设计的好处在于:你不需要在两个地方维护同一份信息。传统博客框架通常要你在配置文件里声明导航结构、在路由文件里定义 URL 规则、在内容目录里组织文件——三个地方说的是同一件事,但你得保持它们同步。这里只有一个来源:文件系统本身。
具体来说:
posts/下的一级目录就是内容类型(article、work、column)- 每个目录的
README.md同时充当该目录的说明页和元数据来源——在 frontmatter 里写上showInNav: true,它就自动出现在导航栏 - 文件路径直接映射为 URL:
posts/column/ai-learning/01-introduction.md→/column/ai-learning/01-introduction - 支持无限层级嵌套(推荐不超过 5 层),每一层都可以有自己的
README.md作为目录页
这套 frontmatter 字段是按我自己的需求设计的——你完全可以根据自己的场景调整。AI 生成前端的时候会读取这些字段来构建导航和页面逻辑。
API 优先
这套系统的所有内容,同时以两种形式存在:
- 页面——人类通过浏览器阅读
- 静态 JSON API——Agent 或工具通过 HTTP 读取
这里的「API 优先」不是说我先写了 API 再写页面,而是说整个系统的数据层被设计成独立于展示层的、可被任意消费的接口。
在 Astro 页面里,数据通过 src/api/content.ts 等 TypeScript 模块获取——getAllEntries()、getEntriesByType()、getBreadcrumb() 这些函数封装了对构建时索引的查询逻辑。同时,构建脚本会把同样的数据导出为静态 JSON 文件,挂在 /api/ 路径下:
/api/entries.json— 所有内容条目/api/entry/<uri>.json— 单篇文章的结构化数据 + Markdown 正文/api/tree.json— 完整的目录树/api/tags.json— 标签频率统计/api/stats.json— 归档统计
这些都是纯静态文件,零运行时依赖。任何能发 HTTP 请求的东西——浏览器、curl、AI Agent、MCP 服务——都能直接读取你的全部档案,不需要额外搭 RAG 管道,不需要跑 embedding 服务。
前端是可替换的
src/pages/ 下的这套前端只是一个参考实现。它做的事情就是调用 src/api/* 的函数,把数据渲染成页面,仅此而已。
系统的核心在 src/api/ 和 Python 构建脚本——只要这两层不动,前端可以整个删掉重写。这也是整个项目最关键的架构决策:把「稳定的数据基座」和「可变的展示层」彻底分离。
这个分离使得「让 AI 重写前端」变成了一件靠谱的事。AI 不需要理解你的整个项目,它只需要知道:数据从哪些 API 来、有哪些字段可用、页面之间的路由关系是什么。这些信息我全部打包在了一个 Claude Code Skill(blog-frontend-bootstrap)里——详细的 Skill 设计和使用方式,在下一篇文章里展开。