三个月前我还在用 Obsidian 散装记笔记,现在我已经有了一个跑在本地、可离线打开、界面精美、还能装成桌面应用的私人知识库——ReverieVault。
整个过程全部由 AI 编程助手 Pi 协助完成,从零开始,没写一行前端代码。这篇文章记录完整的搭建过程、踩过的坑和最终沉淀的方法论,希望能帮你少走弯路。
一、为什么不用现成的知识库工具?
我的需求其实很朴素:
- 内容是我的一手资料(项目文档、研究笔记、随手记录),不是给别人看的博客
- 要能本地离线访问,不要依赖云端服务
- 要有自己的风格,不是千篇一律的模板
- 要能装成应用,像原生软件一样双击打开
市面上的方案: - Notion / 语雀:云端,数据不在自己手里 - Obsidian 发布:付费,且样式定制有限 - Hugo / Jekyll:太"博客",知识库的组织能力弱 - 自己写:成本太高
最后选中了 Quartz——一个把 Markdown 笔记变成静态网站的工具。它有几个杀手级特性:
- 原生支持双向链接(
[[wikilink]]),Obsidian 用户的笔记直接可用 - 内置搜索 + 知识图谱,知识库的灵魂功能
- 861 个 Obsidian 社区主题,想要什么风格都有
- SPA 架构,页面切换无刷新,体验接近原生应用
二、搭建:从源码开始(避开 create-quartz 的坑)
Quartz v5 官方推荐用 npx create-quartz@latest 脚手架,但我执行时发现 npm 仓库里没有这个包(v5 只提供了 GitHub 源码)。所以走了源码路线:
git clone https://github.com/jackyzha0/quartz.git yeatlib-temp
cd yeatlib-temp && npm install
然后手动配置。Quartz v5 的配置是 YAML(quartz.config.yaml),不是 v4 的 TypeScript——这点和网上大部分教程不一样,别搞混。
端口和本地服务
npx quartz build --serve --port 9796 --watch
--watch 模式下改任何 Markdown 自动重建,浏览器即时刷新,体验和 Obsidian 预览几乎一样。
三、内容组织:PARA 管素材,Wiki 管知识
这是我最得意的一层设计。知识库内容怎么组织,决定了它会不会变成一个"电子垃圾堆"。
我采用了 Tiago Forte 的 PARA 方法 + Andrej Karpathy 的 LLM Wiki 理念 的双层结构:
content/
├── 01-Projects/ # 进行中的项目(有截止日期的事)
│ ├── 一叶知秋/ # 孩子的教育规划
│ ├── 如登博客/ # 博客运营
│ └── 国庆骑行/ # 父子骑行计划
├── 02-Areas/ # 持续关注的领域(没有截止日期)
│ ├── 宇宙/
│ ├── 哲学/
│ └── AI/
├── 03-Resources/ # 可复用素材(工具、模板、参考)
├── 04-Archives/ # 归档(已结束的事)
└── 05-Wiki/ # 知识提炼层
核心思想:01-04 是 Raw 层(一手资料),05 是 Wiki 层(提炼后的知识)。
- Raw 层:按 PARA 的"可行动性"组织——正在做的项目最优先(01),其次是长期关注的领域(02),再是可复用的素材(03),最后是归档(04)
- Wiki 层:从 Raw 里提炼出可复用的知识条目,双向链接回来源。比如"国庆骑行"项目的经验 → 提炼成「长途骑行准备清单」Wiki 条目;博客草稿 → 提炼成「Harness / Agent / MCP 概念卡」
数据流是单向的:Raw 素材 → 提炼 → Wiki 条目。Wiki 只放"值得长期记住、可以被 AI 消费"的结构化知识,避免 Wiki 层变成第二个垃圾堆。
数字前缀(01-05)保证侧边栏顺序永远符合优先级,这是 PARA 在文件系统层面的落地。
四、主题与字体:861 个主题随便挑
Quartz v5 通过 @quartz-themes/core 插件加载 861 个 Obsidian 社区主题,而且支持"混搭"——可以只拿某个主题的表格样式、另一个主题的代码块样式。
plugins:
- source: "@quartz-themes/core"
enabled: true
options:
theme: minimal # 主题名
mode: both # 亮色+暗色都支持
主题包需要预装(直接 npm install,否则构建时会自动安装但可能踩依赖冲突):
npm install @quartz-themes/minimal --legacy-peer-deps
我实测了 15 个主题(Tokyo Night、Catppuccin、Everforest、Nord、Monokai……)后选了 minimal——极简、内容优先,适合大量阅读的知识库场景。
字体踩坑记录
中文知识库的字体选择是个大学问。Google Fonts 上可用的中文字体有限,我试了:
- 站酷小薇(ZCOOL XiaoWei):标题很有书卷气,和"锁梦斋"的气质绝配 ⭐
- 思源黑体(Noto Sans SC):正文首选,清晰不疲劳
- 马善政毛笔 / 青刻黄油:个性十足但偏装饰
踩坑:站酷小薇只有 400 一个字重,而 Quartz 默认给标题请求 400+700,导致 Google Fonts 返回 400 错误、字体加载失败。解决:配置里显式指定字重:
typography:
header:
name: "ZCOOL XiaoWei"
weights: [400] # 关键:只请求存在的字重
body: "Noto Sans SC"
五、本地应用化:PWA 的正确姿势
这是折腾最久的部分。我最初做了一个 macOS .app 包装(Chrome --app 模式),能用但很"土"——依赖 Chrome 装壳,而且没法跨平台。后来换成了 PWA(渐进式 Web 应用),这才是正解:
- Chrome/Edge/Safari 原生支持"安装为应用"
- 安装后独立窗口、无地址栏、有 Dock 图标
- 支持离线访问(Service Worker 缓存)
PWA 四件套
Quartz 没有内置 PWA,我手动加了四样东西(放在 quartz/static/,构建时自动复制到站点根):
quartz/static/
├── manifest.json # PWA 清单(应用名、图标、主题色)
├── sw.js # Service Worker(缓存策略)
├── pwa-icon-192.png # 图标
└── pwa-icon-512.png # 高清图标
manifest.json 是 PWA 的身份证:
{
"name": "ReverieVault",
"short_name": "ReverieVault",
"display": "standalone",
"background_color": "#0B192C",
"theme_color": "#0B192C",
"icons": [
{ "src": "/static/pwa-icon-192.png", "sizes": "192x192" },
{ "src": "/static/pwa-icon-512.png", "sizes": "512x512" }
]
}
Service Worker 负责离线缓存。我的缓存策略: - 页面:网络优先,失败回退缓存(保证内容实时) - 静态资源(CSS/JS/图片):缓存优先 - manifest.json 和 sw.js 本身:永远走网络(这点极其重要,见下文坑)
🔥 PWA 最大的坑:改名字/图标不生效
我在修改 PWA 名称(从中文改成语 ReverieVault)时,重装应用后名字还是旧的,折腾了很久。根因是 Service Worker 的缓存策略把 manifest.json 也缓存了!
Chrome 安装 PWA 时读取的 manifest 会被 SW 拦截,返回的是缓存的旧版本。而 SW 版本不升级时,旧 SW 一直持有旧缓存。
三层修复:
- manifest.json 永远走网络,不进 SW 缓存:
const isManifest = url.pathname.endsWith('/manifest.json')
const isStatic = !isManifest && /\.(css|js|png|...)$/.test(url.pathname)
- SW 版本号升级,activate 时清掉旧缓存:
const CACHE_NAME = 'reverievault-v7' // 每次改动版本号
// activate 时删除其他版本的缓存
- 重新安装 PWA(
chrome://apps删除 → 重新安装)
坑 1.5:新增笔记侧边栏不显示(contentIndex.json 缓存)
写知识库的都会遇到:明明新建了笔记,刷新后 explorer 侧边栏里却看不到。折腾到最后发现,根因和 manifest 一模一样——Service Worker 缓存了 /static/contentIndex.json(文件索引),fetch 处理器对它"缓存优先",索引不更新,侧边栏自然不显示新文件。
修复:把 contentIndex.json 也归入"永远走网络"清单:
const isManifest = url.pathname.endsWith('/manifest.json')
|| url.pathname.endsWith('/sw.js')
|| url.pathname.endsWith('/contentIndex.json') // 新增!
const isStatic = !isManifest && /\.(css|js|png|...)$/.test(url.pathname)
经验总结:凡是"内容索引/配置"类资源(manifest.json、contentIndex.json、sw.js),在 SW 里都必须绕过缓存走网络,否则改了内容不生效。
另一个坑:开发模式的 CSS 缓存
Quartz 开发模式(--serve)默认关闭 CSS 的 hash 命名,index.css 文件名不变,改样式后浏览器一直用旧缓存。后来发现根因是代码里 useHashing = !ctx.argv.serve,改成 useHashing = true 后,CSS 文件名带内容 hash,改一次样式浏览器自动拉新文件。
六、最终效果
现在的 ReverieVault:
- 内容:PARA 组织的项目/领域/资源/归档 + Wiki 提炼层
- 主题:minimal 极简风 + 站酷小薇标题字体
- Logo:深蓝底金色 R(品牌色
#0B192C+#D4AF37) - 形态:本地服务 + PWA 可安装桌面应用
- 离线可用:SW 缓存,断网也能读笔记
七、给后来者的建议
- 内容组织先于工具选型——先想清楚 PARA + Wiki 的结构,再决定用什么工具,Quartz 只是载体
- 主题别贪多——15 个主题实测下来,最后用的还是最朴素的 minimal;先选内容为主的主题,再微调字体
- PWA 的 manifest 缓存坑——凡是 PWA 配置(名称/图标),记得让 manifest.json 绕过 SW 缓存,否则改了不生效
- 让 AI 帮你搭建——整个过程我一行前端代码没写,全是和 Pi 对话完成的:它帮我写配置、调 CSS、修 bug、甚至生成图标。AI 时代,工具链的搭建门槛已经低到"说清楚需求"就够了
本文由 Pi 协助整理,搭建过程的完整技术细节记录在 ReverieVault 的 05-Wiki 知识提炼层。

评论(0)
暂无评论,来抢沙发~
请 登录 后发表评论