什么是 Quartz
Quartz 是一个开源的静态网站生成器,官方对自己的定义只有一句话:
Quartz is a set of tools that helps you publish your digital garden and notes as a website for free. (一套工具,帮你把数字花园和笔记免费发布成网站。)
作者是 Jacky Zhao(jzhao.xyz),"数字花园(digital garden)"这个词的流行和他那篇《Networked Thought》关系很大。本仓库当前版本是 Quartz 5(5.0.0),主分支固定叫 v5。
几个必须说清的性质:
- 它不是一个托管服务,也不是 CMS。 它只做一件事:把
content/里的 Markdown 编译成public/里的纯静态 HTML / CSS / JS。 - 产物是纯静态文件,扔给任意静态托管就能跑:Cloudflare Pages、GitHub Pages、Netlify、Vercel,甚至自己一台机器。
- 技术栈是 Node.js(要求 ≥ 22)+ TypeScript,构建走 esbuild,配置就是仓库根目录一个
quartz.config.yaml。 - 插件式架构,本仓库用的
obsidian预设一共有 46 个插件条目,功能开关全在 YAML 里,不用改代码。
链接
| 项目 | 地址 |
|---|---|
| 官方文档(从这开始) | https://quartz.jzhao.xyz/ |
| 官方仓库 | https://github.com/jackyzha0/quartz |
| 「数字花园」概念原文 | https://jzhao.xyz/posts/networked-thought |
| 演示站点 | https://quartz.jzhao.xyz/ |
| 本站(本文实测对象) | https://blog.buzhidao.de5.net/ |
| 本站源码仓库(私有) | https://github.com/buzhidao10068/Quartz |
| 评论数据仓库(giscus) | https://github.com/buzhidao10068/Quartz-comments |
Quartz 是干什么的
把本地笔记库变成一个带搜索、带图谱、能评论的网站,而笔记本身一个字都不用改。
具体它能干的:
① 原样发布 Obsidian 笔记 [[双链]]、#标签、> [!note] callout、Mermaid 图、脚注、表格、任务框、.canvas、.base 全都原生认。hard-line-breaks 还能让"单个换行当 <br>",和 Obsidian 里的观感一致。
② 一个像样的站点该有的导航能力 全文搜索(浏览器端索引,不依赖服务端)、探索栏、关系图谱、反向链接、悬浮预览、目录(TOC)、暗色/亮色切换、多语言界面(内置 zh-CN)。
③ 细粒度的发布控制 draft: true 干脆不生成 HTML;unlisted: true 生成页面但不进列表/搜索/图谱/RSS;explicit-publish 插件可以反过来做成白名单,只有写了 publish: true 的才发;encrypted-pages 还能给单页加密码。
④ 自动生成一堆站点级产物 RSS(index.xml)、sitemap.xml、搜索索引 JSON、favicon、分享卡片图(OG image,用 satori + sharp 在构建时生成)、GitHub Pages 用的 CNAME。
⑤ 什么都不用运维 推一次代码部署一次。没有数据库、没有服务端进程、没有证书要手动续——静态托管把这些全包了。
它不干的事(容易误解的地方):
- 不管评论后台。评论靠 giscus 接 GitHub Discussions,数据存在评论仓库里,Quartz 只负责把评论区嵌入页面。
- 不管托管和域名。域名、HTTPS、CDN 都是托管平台的事。
- 没有在线编辑器。写作始终在 Obsidian / 任意 Markdown 编辑器里,Quartz 只在"从 Markdown 到网站"这一段工作。
一、建仓:走「选项 A」
官方安装文档给了两条路:
| 做法 | origin 指向 | 后患 | |
|---|---|---|---|
| 选项 A(推荐) | GitHub 页面点 Use this template → Create a new repository | 你自己的仓库 | 几乎没有 |
| 选项 B | git clone 官方仓库 |
jackyzha0/quartz |
之后必须手动改 remote,否则 push 打回官方仓 |
选项 A 省掉的正是最容易出错的一步:不用再折腾 git remote set-url origin。建完确认默认分支是 v5 —— Quartz 5 全链路钉死在这个分支名上(CLI 里 QUARTZ_SOURCE_BRANCH = "v5"、CI 触发分支、Cloudflare 的 Production branch 全都是它)。
git clone https://github.com/<你的用户名>/<仓库名>.git
cd <仓库名>
二、装依赖 + 初始化
npm i
npx quartz create
前置条件别忽略:Node.js ≥ 22、npm ≥ 10.9.2。仓库的 .npmrc 开了 engine-strict=true,版本不够时 npm i 会直接硬失败,而不是给个警告。
npx quartz create 会依次问四件事,我的选择:
| 问题 | 我选的 | 理由 |
|---|---|---|
| 模板 | obsidian |
笔记库直接搬过来,硬换行、双链、标签全对齐 Obsidian 行为 |
| content 策略 | new(自己写) |
后来直接在 content/ 里写;要做软链得先开 Windows 开发者模式 |
| baseUrl | blog.buzhidao.de5.net |
不带 https://,尾部斜杠也会被自动剥掉 |
| 链接解析 | shortest |
和 Obsidian 的 [[页面名]] 写法一致 |
等价的一行非交互写法:
npx quartz create --template obsidian --strategy new --baseUrl blog.buzhidao.de5.net
[!danger]
create是整份覆盖quartz.config.yaml
不是合并,官方文档原话就是 “always overwrites”。所有手改过的 locale、字体、评论参数都会被打回模板默认值。已经改过配置的话,先备份。
四个模板的差别其实很小(我把三份 YAML 逐条 diff 过):
| 模板 | 条目数 | 相对 default 的差别 |
|---|---|---|
default |
45 | 基准 |
blog |
45 | +comments、recent-notes;−encrypted-pages |
obsidian |
46 | +hard-line-breaks、@quartz-themes/core;+Excalidraw |
ttrpg |
47 | =obsidian + Leaflet 地图(git 来源) |
也就是说,"blog 模板"的全部含义就是多开了评论区和一个"最近的笔记"侧栏。想要 blog 风格不用重跑 create,只要把这两条 enabled: false 改成 true 就行 —— 这也顺便躲开了配置被覆盖的风险。
三、中文化
Quartz 内置 zh-CN 翻译,改一行就够:
configuration:
locale: zh-CN
字体是另一个要注意的点:默认排版用的是 Schibsted Grotesk / Source Sans Pro,不含中文字形,中文会回退到系统字体(Windows 上就是微软雅黑),中英文风格不统一。
theme:
fontOrigin: local # 不依赖 Google Fonts,大陆访问更稳
fontOrigin 只有 googleFonts 和 local 两个合法值。选 local 就直接吃系统字体,不请求外部 CDN;想让中英文统一,可以换 Noto Sans SC,但那就得走 Google Fonts 或者自己托管字体文件。
四、本地预览
npx quartz build --serve --port 3005 --wsPort 3006
--port默认 8080,我习惯用 3005;- 热重载走 WebSocket,默认占 3001,被占用时加
--wsPort 3006; --serve隐含 watch,改 Markdown 自动重建并刷新浏览器。
五、我这台机器专属的坑
这一节是我实际撞上的,Quartz 本身没问题,纯粹是环境。如果你也在 Windows + 沙箱终端里跑,可能会遇到同款。
坑 1:mkdir quartz\.quartz-cache: Access is denied
构建一开始就死:
✘ [ERROR] Failed to create output directory:
mkdir ...\quartz\.quartz-cache: Access is denied.
根因排查了很久,最后定性为 Windows 完整性级别(MIC):工作区根目录被打上了会向下继承的 Low 标签,于是根目录下新建的 node_modules 也继承 Low,里面每次启动的 esbuild.exe 都只能是 Low 级别;而 clone 时就存在的 quartz\、docs\ 仍是默认 Medium —— Low 进程写 Medium 目录被 Windows 的"禁止向上写"拦掉。
两个绕法,任选一个:
# A. 把 node_modules 的完整性级别提回 Medium(几千个文件,几秒完成)
icacls node_modules /setintegritylevel "(OI)(CI)Medium" /T
# B. 把 esbuild.exe 复制到临时目录,用环境变量指向它
$tmp = "$env:TEMP\esbuild\esbuild.exe"
Copy-Item node_modules\esbuild\esbuild.exe $tmp
$env:ESBUILD_BINARY_PATH = $tmp
[!warning] 这个坑的关键特征
换成"完全权限"跑也修不好,重装node_modules也修不好 —— 因为新目录仍然继承 Low。别在提权上浪费时间,直接动完整性标签。
坑 2:npx quartz ... 卡住直到 OOM
npx quartz --version 都可能几分钟没有任何输出,最后 FATAL ERROR: Ineffective mark-compacts near heap limit。
判据很明确:崩之前一行 quartz 输出都没有,说明崩在启动之前,不是构建吃内存。原因是 npx 缓存里给这个包建了一个指向仓库根的 Junction,每次校验都要把几千个文件、两百多兆走一遍。
绕过方式:直接调入口脚本,完全等价。
node ./quartz/bootstrap-cli.mjs build --serve --port 3005
(npm cache clean --force 也能修,但要重新下载。)
坑 3:--serve 模式下样式和脚本全 404,页面变成单列
页面能打开,但布局是一列、搜索框点不动、探索栏空白。DevTools 里能看到 index.css、postscript.js 全是 404。
原因是资源命名不一致:--serve 用不带哈希的名字(index.css),生产构建用带哈希的名字(index-bb6c1746.css)。如果 public/ 里残留了生产构建的产物,serve 模式引用的那三个文件就不存在。而写盘异常在 emit 阶段是被静默吞掉的,所以不报错。
更隐蔽的是:主样式表丢了 = 没有 #quartz-body 的网格规则 = 页面永远单列,跟窗口宽度无关。我一开始误判成"窗口太窄",绕了一大圈 —— 排查时先看 Network 里 index.css 是不是 200,能省很多时间。
修复:
# 先 Ctrl+C 停掉 dev server,然后
Remove-Item public -Recurse -Force
npx quartz build --serve --port 3005
顺手记一条纪律:开着 dev server 时不要另跑一次 npx quartz build(生产构建会重写 public/ 里的资源名,两边打架)。要单独验证构建,可以输出到别的目录:npx quartz build -o .build-check。
坑 4:quartz.lock.json 是空的,还以为是坏了
{ "version": "1.0.0", "plugins": {} }
这是正常的。lockfile 只登记 git 来源的插件(github:owner/repo、git+https://…、本地路径);Quartz 5 绝大多数插件是 npm 包,版本由 package.json + package-lock.json 管,不进 lockfile。所以纯 npm 配置下 .quartz/plugins/ 是空的、lockfile 也是空的,都对。
六、评论:giscus
Quartz 的 comments 插件只支持 giscus 一种 provider,而 giscus 的两个硬要求容易踩:
- 评论仓库必须是 public。站点源码可以私有,但评论要单独开一个公开仓库放 Discussions。
- 仓库要开启 Discussions,并装 giscus App。
我的配置:
- source: "@quartz-community/comments"
enabled: true
options:
provider: giscus
options:
repo: buzhidao10068/Quartz-comments
repoId: R_kgDOU74DEQ
category: Announcements
categoryId: DIC_kwDOU74DEc4DHDSJ
mapping: pathname # 关键
strict: true
reactionsEnabled: true
inputPosition: bottom
lightTheme: light
darkTheme: dark
lang: zh-CN
layout:
position: afterBody
priority: 10
三个值得说的选择:
① mapping: pathname 而不是默认的 url。 评论跟"页面路径"绑定,不跟域名绑定。我从 blog2.buzhidao.de5.net 换成 blog.buzhidao.de5.net 时,评论一条没丢 —— 因为 /notes/abc/ 这个键没变。用 url 的话,域名一改就分成两套,旧评论等于清零。
② category: Announcements。 这个分类默认只有维护者能新建讨论,可以防止别人在你仓库里乱建帖子;giscus bot 有权限,不受影响。
③ 评论数据不在你的站点仓库里。 它存在评论仓库的 Discussions 里 —— 一个页面对应一条 Discussion,一条评论就是一条回复。GitHub 就是你的评论后台,回复、删除、锁定都在那里做。
[!note] 两条使用提醒
- 没人评论时 Discussions 是空的,这很正常 —— 第一条评论发出时 giscus 才会创建对应讨论。
- 本地
localhost测试时别真发评论:pathname映射下,本地/和线上首页的/是同一条讨论。
七、部署到 Cloudflare Pages
从 GitHub 拉代码构建,推一次部署一次。
| 配置项 | 值 |
|---|---|
| Production branch | v5 |
| Framework preset | None |
| Build command | git fetch --unshallow && npx quartz plugin install --from-config && npx quartz build |
| Build output directory | public |
| 环境变量 | NODE_VERSION = 24 |
构建命令里两段前缀都不是可选的:
git fetch --unshallow—— Cloudflare 默认浅克隆,不加这行created-modified-date从 git 取日期会不准;plugin install --from-config—— 按配置补装缺失插件。注意它只处理 git 来源的插件,npm 插件靠构建前的npm ci自动装好。而且装失败只打警告、不会让构建失败,所以构建成功 ≠ 插件生效,日志里的
要自己看一眼。
绑自定义域名
Pages 项目 → Custom domains → Set up a domain → 填域名。分两种情况:
- 域名 DNS 已在 Cloudflare:自动加记录、自动签证书,直接生效。apex 域名(
example.com)只能走这条,Cloudflare 会用 CNAME 展平。 - DNS 在别处:子域名加一条
CNAME指向<项目名>.pages.dev;apex 域名不能直接写 CNAME,要么把 nameserver 迁到 Cloudflare,要么看服务商是否支持ALIAS/ANAME。
[!warning] 顺序不能反
必须先在 Pages 面板关联域名,再去加 DNS 记录。只在 DNS 里手动加 CNAME 会得到 522。
域名换好后,quartz.config.yaml 里的 baseUrl 要同步改(不带协议、不带尾斜杠)——它决定 sitemap、RSS、分享卡片图(OG)和 giscus 主题 CSS 的地址。
旧域名建议保留并做 301:RSS 订阅者订阅的是具体地址,不会自动跟着换域名
八、日常写作流程
# 1) 在 Obsidian 里写(把 content/ 直接当 vault 打开,我是打开content的父文件夹)
# 2) 本地预览
npx quartz build --serve --port 3005 --wsPort 3006
# 3) 发布
npx quartz sync -m "content: 新增文章标题"(发布就是git操作了)
sync 的动作是 git add -A + commit + pull + push。默认提交信息是 Quartz sync: <时间戳>,想写自己的就加 -m。
[!danger]
sync是强推,而且推的是"当前分支"
代码里是git push -uf origin $(git rev-parse --abbrev-ref HEAD),但 pull 固定走origin v5。所以跑之前一定确认git branch --show-current是v5,否则会变成"拉 v5、强推别的分支"。
每篇文章的 frontmatter
---
title: 文章标题
description: 一句话摘要,用于搜索和分享卡片
tags: [Quartz, 博客搭建]
created: 2026-10-26
modified: 2026-10-26
published: 2026-10-26
enableToc: true
comments: true
draft: false
unlisted: false
password:
stealth: false
publish: true
数字编号:
---
几个容易踩的点:
| 字段 | 注意 |
|---|---|
publish |
如果开了 explicit-publish 插件,不写 publish: true 就完全不会生成页面 —— 这是最容易"文章消失了却找不到原因"的一处 |
tags |
必须写成数组(tags: [a, b]),写成单值会让"有标签才显示"这类条件失效 |
| 日期 | 只认 created / modified / published,Obsidian 常见的 date: 字段 Quartz 不读 |
draft vs unlisted |
draft: true 连 HTML 都不生成;unlisted: true 生成页面但不进列表/搜索/图谱,直链能访问 |
| 大小写 | enableToc、cssclasses、socialImage 写成 enable_toc / CSSClasses 都不生效 |
九、最终成果
| 项目 | 结果 |
|---|---|
| 站点 | 静态 HTML,托管在 Cloudflare Pages |
| 域名 | 自定义域名 + 自动 HTTPS |
| 界面语言 | zh-CN,日期按中文格式化 |
| 评论 | giscus,数据存 GitHub Discussions,换域名不丢 |
| 写作方式 | Obsidian 直接写 Markdown,content/ 当 vault |
| 运维成本 | 0(推代码即部署) |
| 花费 | 0 |
同步自我的blog:https://blog.buzhidao.de5.net/quartz-5相关/从零到上线:quartz-5-博客搭建全过程总结