为什么做这个系统
我想要的不是「再搭一个博客」,而是把四件事统一在一个仓库里:
- AI Agent 友好操作 —— 让模型能直接读、写、改我的笔记和文章,不需要我做格式转换
- 个人知识库积累 —— 长期沉淀,单向只增不减,按主题而不是按时间组织
- 从笔记到成品文章的输出 —— 草稿、卡片、文献笔记最终能凝练成可发布的内容
- 发布到公网 —— 让一部分内容走出私域,对外可读
传统做法是「笔记软件 + 博客系统」两套割裂的工具,写完笔记还要复制、改格式、再 push 一次。中间这层手工搬运是低价值的劳动,也是我最容易放弃更新的地方。
这个系统的核心目标,就是消除中间这层搬运。
整体数据流
Obsidian / AI Agent 写作
-> knowledge/06-Posts/*.md (单一内容源)
-> Astro 构建静态站点
-> git push origin main
-> Cloudflare Pages 自动部署
-> 浏览器访问
整条链路只有一个内容源、一次构建、一次发布。我在 Obsidian 里做的所有改动,最终都会以同一份 Markdown 的身份出现在公网上 —— 这是最重要的设计前提。
知识库的设计:分层而不是分类
知识库目录采用 PARA 风格的编号结构:
00-Inbox/临时收集,未消化01-Projects/当前主动推进的主题02-Areas/长期关注主题03-Resources/文献笔记04-Notes/永久笔记(卡片)05-MOC/主题导航页06-Posts/博客文章来源99-Archive/归档
这套分层不是按「话题」分的,是按「信息的成熟度」分的:
- Inbox 是原料
- Resources / Notes 是半成品
- Posts 是成品
一条信息从 Inbox 进入,逐步精炼成卡片(Notes),多张卡片在 MOC 里被组织起来,最终被提炼成一篇 Posts。这个流向是单向的,写作不再是从空白页开始,而是从已有卡片中拼装。
我做的取舍是:不允许「话题文件夹」。比如不会有一个叫 Programming/ 的目录把所有编程相关都塞进去。话题归属交给 tag 和 wikilink,目录只负责「这条信息处于什么阶段」。这样同一条笔记不会在多个目录之间纠结放哪。
博客只读 06-Posts/,其他目录不参与发布
所有可能公开的内容必须明确进入 06-Posts/,其他目录天然在公网视野之外。这条规则带来两个直接好处:
- 物理隔离:Inbox 里的胡思乱想、Resources 里的版权敏感摘抄,不可能因为忘记设置
publish字段而泄露 - 写作意图明确:把笔记搬到
06-Posts/是一个有仪式感的动作,意味着「我决定要让别人看到这个」
publish 护栏:默认不发布
即使文章已经在 06-Posts/ 里,也不会自动公开。每篇文章 frontmatter 必须显式声明:
publish: true进入站点publish: false或缺失字段 → 不进入
「默认 false」这个选择是有意的。它的反面 ——「默认 true,要藏才设 false」—— 看起来更省事,但它把出错方向定在了「误公开」上。一旦写出来才发现不合适,回收的成本远高于多打几个字 publish: true。
这是一道配合目录隔离的双层护栏:先决定「是否搬进 Posts」,再决定「是否打开开关」。两步都需要明确动作,没有任何一步可以靠默认行为完成发布。
Wikilink:保留 Obsidian 的连接性
Obsidian 的灵魂是 [[这种语法]],让笔记之间形成图谱。如果发布时把 wikilink 当作未知语法降级成纯文本,等于砍掉了 Obsidian 在公网这一侧的延伸。
所以构建时做了一层处理:
- wikilink 目标对应一篇已发布的文章 → 转成
/posts/<slug>/的真实链接 - 目标不存在或文章未发布 → 降级为纯文本,避免死链
这样我在 Obsidian 里写文章时不需要切换语法,所有笔记里的链接习惯可以无缝带到博客里。同时这层处理保证了「未发布的笔记永远不会从已发布的文章里被链接到」—— 这又是一道护栏。
构建前检查:让发布意图显式化
每次构建前会自动跑一个脚本,扫描 knowledge/06-Posts/,列出所有即将发布的文章 —— 标题、文件路径、字数。
它不修改任何东西,只是把发布意图打印出来。
这个脚本的存在不是为了发现错误,而是为了在我即将把内容推到公网前,给我一次「看一眼,是这些吗?」的机会。设计上很轻量,但它消除了一种特定的恐慌:「我刚才那次 push 到底发了什么出去?」
目录结构:blog/ 和 knowledge/ 平级,不嵌套
仓库根目录长这样:
/
├── blog/ # Astro 项目,包含 node_modules
├── knowledge/ # Obsidian vault
├── scripts/ # 构建检查脚本
└── ...
最初的直觉是把 blog/ 放进 knowledge/ 里面,这样「整个 vault 就是仓库」。但这会带来一个非常具体的问题:Obsidian 启动时会扫描 vault 下的所有文件,遇到 blog/node_modules/ 那十几万个文件会卡到不可用。
所以二者分开放,knowledge/ 是 Obsidian 的世界,blog/ 是 Node.js 的世界,互不干扰。Astro 在构建时跨目录读取 ../knowledge/06-Posts/,这种「跨目录读取」看起来不优雅,但解决了一个真实的体验问题。
这是一个典型的「为现实让步」的取舍:不追求结构上的纯净,而追求每天打开 Obsidian 的那 200 毫秒体验。
Cloudflare 这一段:从 push 到上线全自动
发布侧用 Cloudflare Pages,关键的几次配置:
Pages 授权 GitHub —— 一次性。授权时建议只选这一个仓库,不开放整个 GitHub 账号的访问权。
构建配置 —— 三件事讲清楚就够了:根目录是 blog/,构建命令是 npm install && npm run build,输出目录是 dist。Cloudflare 的免费额度对个人博客来说是溢出的。
域名 NS 迁移 —— 把域名的 nameservers 从原 DNS 服务商(我这里是腾讯云)切到 Cloudflare 自己的两个 NS 上。这不是改一条解析记录,是把整个域名的解析权交给 Cloudflare。代价是「不能再在原服务商面板上改解析」,收益是「DNS、CDN、SSL 全部一站式」,对单人写作来说这笔交换很划算。
SSL —— 全自动签发 Let’s Encrypt 证书。唯一要主动确认的是 SSL/TLS 模式必须是 Full(或 Full strict),不能是 Flexible,否则会出现奇怪的重定向死循环。
设置完成后,发布的全部动作就是:
git push origin main
GitHub webhook 通知 Cloudflare → clone 仓库 → 进 blog/ 跑 build → 部署到全球边缘节点。整个过程 1–3 分钟,没有手动步骤,也没有可以出错的地方。
这套系统故意不做的事
回过头看,这套系统在「最小可用」的边界上停得很早:
- 没有自动转换脚本(因为内容已经是标准 Markdown)
- 没有定时发布(因为
publish: true+ push 已经足够) - 没有主题美化(因为 AstroPaper 默认主题已经够看)
- 没有评论、没有搜索、没有访问统计(因为暂时不需要)
每砍掉一项,就少一份维护成本。当我自己也是这套系统的唯一用户时,克制比丰富更有价值。
写在最后
这个系统的设计目标,从头到尾只有一个:让我能像写笔记一样去发文章,让 AI Agent 能像编辑文档一样去编辑公开内容,而不需要在「私域笔记」和「公开博客」之间维持两套心智模型。
它不是为了成为一个更好的博客系统,而是为了让我多写。