把個人知識庫建起來並公開發布(Quartz v5 + Cloudflare Pages)
我要的東西很具體:一個能邊學邊記、記完就自然變成公開站點的地方,不要訂閱費、不要自己維護伺服器、不要依賴任何公司的雲端。最後落在 Quartz + Cloudflare Pages 上,整條路徑零費用。
這篇是搭建手册,但我把我判斷錯的地方也寫進去 —— 那些才是這篇有用的部分。
為什麼是這套組合
| 選擇 | 理由 | 我排除掉的替代方案 |
|---|---|---|
| Markdown 純文字作為唯一真相源 | 十年後還能讀、能 grep、能被任何工具處理 | Notion:資料在別人的資料庫裡,導出困難,且兩個真相源的同步終究會殺死個人知識庫 |
| Obsidian 只做編輯器 | 離線可用、雙向連結體驗好、不锁定格式 | 另建一個 Obsidian vault 再同步到 content:不要這樣做,直接讓 content/ 本身成為 vault |
| Quartz | 靜態站、支援 wiki-link、圖譜、搜索、後端链接都是内置 | 自己寫生成器:得不到的東西遠多於省下的時間 |
| Cloudflare Pages | 免費、push 即部署、自帶全球 CDN 與 HTTPS | 自建伺服器:多一個要patch的東西,而且我上傳帶寬再大也不該拿去當公網服務 |
一、起手:用 template,不要 fork
Quartz 的仓库有 Use this template。點它,生成一個屬於你的、只有一個初始提交的私有仓库,再 clone 下來。
我 fork 過一次,後來改掉了。差別很實際:fork 帶著上游幾百個提交,之後每次 git log、每次「最後修改時間」的計算、每次跟上游比對都要穿過那堆不属于你的歷史。而你要跟进上游的能力,本來就不是靠 fork,是靠讀它的 changelog。
預設分支是 v5,不是 main。這一件小事会在后面部署时咬你一次(見第五節)。
npm i
npx quartz createnpx 跑的是本项目 node_modules/.bin 里的可执行文件,不需要全局安装任何东西。quartz create 会生成 content/index.md 與 quartz.config.yaml。
二、v5 最大的差異:配置只有一份 YAML
v4 的配置是 TypeScript(quartz.config.ts)。v5 換成了單一的 quartz.config.yaml,插件也從「內建模組」變成「可插拔單元」。
这里我踩了第一個誤判:仓库里同時存在 quartz.config.default.yaml。那份是上游預設值,不要改它,你的配置只寫在 quartz.config.yaml。
我實際改的三項:
| 鍵 | 我原本的值 | 改成 | 為什麼 |
|---|---|---|---|
configuration.baseUrl | github.com/<倉庫路徑>.git | <你的子域名>.pages.dev | quartz create 從 git remote 自动抓的,抓錯了:baseUrl 要填網站域名,不是仓库地址。它影響 sitemap、RSS、canonical 與 OG 標籤 |
configuration.locale | en-US | zh-TW | 界面文字(搜尋佔位符、404、日期格式) |
configuration.pageTitle | Quartz 5 | 自己的站名 | 瀏覽器標籤與首頁標題 |
baseUrl 不带 https://,也不带首尾斜杠 —— 這是上游文件明写的约定。
還有一項得手动清:footer 插件的 options.links 預設硬編碼了上游作者的仓库與 Discord。你的站點頁腳不該挂著別人的連結。
三、第二個誤判:以為插件要自己裝
v5 的功能全在插件裡(搜索、圖譜、後链、目录树……v4 是內建的),於是我看到 .quartz/plugins/ 是空的、quartz.lock.json 不存在,就下了結論:「插件沒装,站點會缺功能」,還準備了一條安装命令。
這條結論是錯的。 實際跑下去报錯,我才去讀 docs/cli/plugin.md 與 package.json:
- 配置裡 source 写成
@quartz-community/xxx這種以@開頭的字串,是 npm 依賴,它们在package.json的dependencies里,npm i就跑完了 —— 你的站点里大部分插件早就装好了 .quartz/plugins/與quartz.lock.json只服務於从 git 源装的那類插件(npx quartz plugin add github:用戶/仓库)- 而且
.quartz/在.gitignore裡,所以它在你的仓库裡本來就應該是空的 —— 那不是缺陷,那是設計
這條誤判值得記
「我預期它是空的」與「它是空的所以我得修」是兩件不同的事。空目錄有兩種:待填的、與本就不該有東西的。 判据只能来自代碼或文件,不能来自直覺。
四、中文搜索不需要任何配置
早期版本裡「要給 Quartz 装 CJK 分詞」那一步作廢。Quartz 核心對中文是逐字分詞(character-by-character tokenization),内建行為。我是在仓库的測試檔里看到那条测试用例名字才確信的 —— 讀測試比讀教程可靠,因為测试是會被執行到的文檔。
五、目錄結構,與一條關於機密的規則
content/
index.md ← 首頁
payments/ ← 公開標準可推导的領域知識
integration/ ← 結算檔案、對帳、MQ、批次、冪等
spring/ ← 企業級框架主線
database/ ← MySQL、索引、執行計畫、事務隔離
nodejs/ ← 副線
ops/ ← 部署基本功、排障方法論(本檔所在)
android/ ← 本職
ai-notes/ ← 與 AI 對話裡「自己不會的知識點」的落點
private/ ← 🔴 永不構建、永不提交
templates/ ← 筆記模板
content/private/ 是這篇最重要的一節。
quartz.config.yaml 的 ignorePatterns 預設含 private,.gitignore 也含 private/。用途很直白:公開站點的讀者包含搜尋引擎、獵頭與雇主,他們不是同一群人。 我給自己定的判斷標準只有一句:
這段內容能不能從公開規範文檔推得出來? 推不出来就不要寫到公開區。判斷不了就丟
private/—— 以後可以往外搬,反過來收不回來。
而這個默認值我没有相信,實測了四遍才算数:
git check-ignore確認private/真被忽略- 在
content/private/放一個只含標記字串的檔案(內容就是隨機字元加一個不可能出現在正常文章裡的詞) npx quartz build,然後在public/里 grep 那個標記 —— No files found- 直接訪問
public/private/—— 目錄根本不存在
這四步花不了五分鐘,但它撑起「我可以安心寫筆記」整個前提。對安全邊界,默認值必須被實測推翻或確認,不能被他讀過就算。 验完記得把 canary 檔删掉。
六、本地驗證
npx quartz build --servebuild 把 content/ 編譯進 public/;--serve 顺手起一個本地 HTTP 伺服器(不加就只生成檔案、不起服務)。開 http://localhost:8080,看完 Ctrl+C。
我的驗收清單(每項都是能客觀判定的):
- 搜中文關鍵詞能命中文章
- 雙向連結可跳轉、圖譜與後链有内容
- 頁面原始碼裡
lang="zh"、標題是自己填的站名、頁腳沒有作者的連結 - 手機连同 WiFi 能打開
最後一項常被省略,但它测的是「生成的資源有絕對路徑依賴嗎」—— 這類問題只在别的機器上才露出來。
七、提交前:先看清楚要推什麼
我差點漏掉的一件事:quartz.config.yaml 是新檔案,沒有 git add 就不會进仓库。 後果不是本地壞(本地照樣能跑),而是雲端 clone 時拿不到它,退回上游預設配置 —— 站点會構建成功,但你的 baseUrl、locale、站名全部失效。
「本地好、雲端壞」最常見的成因
不是腳本寫錯,是生成物或被忽略目錄的差別。
.quartz/、node_modules/、public/都是 gitignore 的,所以每次雲端构建都要重新產生。這也解释了下一節的构建命令為什麼那麼長。
git add -A
git status
git commit -m "配置站點並寫入首批筆記"
git pushgit status 要在 commit 之前跑,不是事後看。我用的兩個客觀判据:
git ls-files quartz.config.yaml必須有輸出(证明配置真进了仓库)git ls-files content/private必須為空
如果 git status 裡出現 public/、node_modules/ 或 content/private/ 的任何檔案,表示忽略規則没生效,停下來先查 —— 後者尤其嚴重。
八、Cloudflare Pages:參數與那個必須额外設的變量
| 選項 | 值 | 含义 |
|---|---|---|
| Production branch | 你的預設分支(我的是 v5,不是 main) | 监聽哪個分支的 push。填錯就是 push 了不構建 |
| Framework preset | None | 按純静态檔案處理 |
| Build command | git fetch --unshallow && npx quartz plugin install --from-config && npx quartz build | 見下方拆解 |
| Build output directory | public | 只發布這個目錄,它是純生成物 |
Build command 三段用 && 串起來(前一条成功才執行后一條):
git fetch --unshallow—— Cloudflare 默认浅克隆(只抓最近幾個提交)。而 Quartz 的「最後修改時間」是靠git log算的,不補完整歷史,每篇文章的日期都會顯示成構建當天。npx quartz plugin install --from-config—— 因為.quartz/不在仓库裡,雲端每次都得重建。--from-config的意思是「以quartz.config.yaml为准装」(預設行為是读quartz.lock.json,而那個檔案在 gitignore 目錄裡,雲端没有)。npx quartz build—— 生成站点。
官方文件沒列、但你的仓库會逼你失敗的一項:Environment variable NODE_VERSION = 22。
理由能从他自己的仓库查出來,不是猜的:
.npmrc第一行是engine-strict=truepackage.json的engines要求node: ">=22"
engine-strict=true 的意思是 「Node 版本不符就直接終止安装」,不是警告。構建機的預設 Node 偏舊,不设這個變量会在 npm install 階段當場失败。你本地是新 Node 所以永遠看不到這個問題 —— 雲端是另一台機器。
授權 GitHub 時只勾這一個仓库,不要給全部。
九、上線後複驗(這一步才是真的驗收)
- 無痕窗口打開线上 URL,搜你的敏感字串(真實公網 IP、MAC、機構名稱、標記字串),搜不到才算過
- 线上直接訪問
<站點>/private/<檔名>—— 必須 404 - 手機用行動網路(不連家裡 WiFi)能打開 —— 证明真的上了公網
- 关掉本地
--serve
前三項都不是「應該沒問題」的層級。公開發布之後才發現要撤,成本是無限大,因為搜尋引擎已经爬走了。
十、花銷
0 元。 Cloudflare Pages 免費層、.pages.dev 子域名自带、不需要注册域名、不需要伺服器。這一路唯一要付錢的環節(買域名、上访问控制)我暫時都不需要。
幾個我會回頭補的洞
- 站點訪問控制:目前靜態站點人人可讀。只要開始寫任何可能敏感的內容,它就從「以後再說」变成前置條件。
private/的備份:它被 gitignore,所以不進仓库、沒有异地備份。丢了損失最大的恰恰是這部分。得单独安排(另一個私有仓库,或對象存儲)。- 真正的異地備份:原始筆記才需要備份,
public/可以随時重建。
相關
排查背後的思路 —— 一個假設是怎麼被實測推倒的:家用網路瓶頸排查:從「打不開」到跑滿千兆