<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Posts on Just Do It</title>
    <link>https://blog.heartleo.dev/posts/</link>
    <description>Recent content in Posts on Just Do It</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <lastBuildDate>Mon, 20 Jul 2026 14:00:00 +0800</lastBuildDate>
    <atom:link href="https://blog.heartleo.dev/posts/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>从零搭一个 Hugo 博客并部署到 Cloudflare Workers</title>
      <link>https://blog.heartleo.dev/posts/how-i-built-my-blog/</link>
      <pubDate>Mon, 20 Jul 2026 14:00:00 +0800</pubDate>
      <guid>https://blog.heartleo.dev/posts/how-i-built-my-blog/</guid>
      <description>Hugo + PaperMod + Cloudflare Workers + giscus，push 即上线，全程不碰服务器。按依赖顺序一步步给出正确的操作和验证方法，含中文站必改的两处配置和 CI 里两处必须钉死的版本。</description>
      <content:encoded><![CDATA[<p>这篇是操作手册。目标形态：写完 markdown，<code>git push</code>，几十秒后上线，
中间不维护任何进程，评论也不需要数据库。</p>
<p>按依赖顺序讲，不做完上一步下一步就没法开始。每步给命令和验证方法。</p>
<p>技术栈：</p>
<ul>
<li>Hugo 生成静态站，单个二进制，22 页构建约 100 毫秒</li>
<li>PaperMod 主题，开箱就是扁平时间流加搜索加深色模式</li>
<li>Cloudflare Workers 托管，静态资源免费且走全球 CDN</li>
<li>giscus 评论，落在 GitHub Discussions，零后端</li>
<li>GitHub Actions 部署，push 触发</li>
<li>D2 画图，构建期出 SVG，读者侧零 JS</li>
</ul>
<figure class="post-img post-img--inline" role="img" aria-label="构建与部署流水线：markdown、图片和 hugo.toml 进入 hugo 构建，产出 public 目录，经 wrangler deploy 推到 Cloudflare Workers，再由 custom_domain 绑到 blog.heartleo.dev；git push main 触发 GitHub Actions，在 CI 里跑同一条流水线">
  <!-- 由 npm run diagrams 生成，不要手改。d2-src-sha256:04c620d54eb40f8d -->
<?xml version="1.0" encoding="utf-8"?><svg width="763" height="1179" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-d2-version="v0.7.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 763 1179"><svg class="d2-2051738425 d2-svg" width="763" height="1179" viewBox="-15 -25 763 1179"><style type="text/css"><![CDATA[
.d2-2051738425 .text {
	font-family: "d2-2051738425-font-regular";
}
@font-face {
	font-family: d2-2051738425-font-regular;
	src: url("data:application/font-woff;base64,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");
}
.d2-2051738425 .text-bold {
	font-family: "d2-2051738425-font-bold";
}
@font-face {
	font-family: d2-2051738425-font-bold;
	src: url("data:application/font-woff;base64,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");
}
.d2-2051738425 .text-italic {
	font-family: "d2-2051738425-font-italic";
}
@font-face {
	font-family: d2-2051738425-font-italic;
	src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape {
  shape-rendering: geometricPrecision;
  stroke-linejoin: round;
}
.connection {
  stroke-linecap: round;
  stroke-linejoin: round;
}
.blend {
  mix-blend-mode: multiply;
  opacity: 0.5;
}

		.d2-2051738425 .fill-N1{fill:#0A0F25;}
		.d2-2051738425 .fill-N2{fill:#676C7E;}
		.d2-2051738425 .fill-N3{fill:#9499AB;}
		.d2-2051738425 .fill-N4{fill:#CFD2DD;}
		.d2-2051738425 .fill-N5{fill:#DEE1EB;}
		.d2-2051738425 .fill-N6{fill:#EEF1F8;}
		.d2-2051738425 .fill-N7{fill:#FFFFFF;}
		.d2-2051738425 .fill-B1{fill:#0D32B2;}
		.d2-2051738425 .fill-B2{fill:#0D32B2;}
		.d2-2051738425 .fill-B3{fill:#E3E9FD;}
		.d2-2051738425 .fill-B4{fill:#E3E9FD;}
		.d2-2051738425 .fill-B5{fill:#EDF0FD;}
		.d2-2051738425 .fill-B6{fill:#F7F8FE;}
		.d2-2051738425 .fill-AA2{fill:#4A6FF3;}
		.d2-2051738425 .fill-AA4{fill:#EDF0FD;}
		.d2-2051738425 .fill-AA5{fill:#F7F8FE;}
		.d2-2051738425 .fill-AB4{fill:#EDF0FD;}
		.d2-2051738425 .fill-AB5{fill:#F7F8FE;}
		.d2-2051738425 .stroke-N1{stroke:#0A0F25;}
		.d2-2051738425 .stroke-N2{stroke:#676C7E;}
		.d2-2051738425 .stroke-N3{stroke:#9499AB;}
		.d2-2051738425 .stroke-N4{stroke:#CFD2DD;}
		.d2-2051738425 .stroke-N5{stroke:#DEE1EB;}
		.d2-2051738425 .stroke-N6{stroke:#EEF1F8;}
		.d2-2051738425 .stroke-N7{stroke:#FFFFFF;}
		.d2-2051738425 .stroke-B1{stroke:#0D32B2;}
		.d2-2051738425 .stroke-B2{stroke:#0D32B2;}
		.d2-2051738425 .stroke-B3{stroke:#E3E9FD;}
		.d2-2051738425 .stroke-B4{stroke:#E3E9FD;}
		.d2-2051738425 .stroke-B5{stroke:#EDF0FD;}
		.d2-2051738425 .stroke-B6{stroke:#F7F8FE;}
		.d2-2051738425 .stroke-AA2{stroke:#4A6FF3;}
		.d2-2051738425 .stroke-AA4{stroke:#EDF0FD;}
		.d2-2051738425 .stroke-AA5{stroke:#F7F8FE;}
		.d2-2051738425 .stroke-AB4{stroke:#EDF0FD;}
		.d2-2051738425 .stroke-AB5{stroke:#F7F8FE;}
		.d2-2051738425 .background-color-N1{background-color:#0A0F25;}
		.d2-2051738425 .background-color-N2{background-color:#676C7E;}
		.d2-2051738425 .background-color-N3{background-color:#9499AB;}
		.d2-2051738425 .background-color-N4{background-color:#CFD2DD;}
		.d2-2051738425 .background-color-N5{background-color:#DEE1EB;}
		.d2-2051738425 .background-color-N6{background-color:#EEF1F8;}
		.d2-2051738425 .background-color-N7{background-color:#FFFFFF;}
		.d2-2051738425 .background-color-B1{background-color:#0D32B2;}
		.d2-2051738425 .background-color-B2{background-color:#0D32B2;}
		.d2-2051738425 .background-color-B3{background-color:#E3E9FD;}
		.d2-2051738425 .background-color-B4{background-color:#E3E9FD;}
		.d2-2051738425 .background-color-B5{background-color:#EDF0FD;}
		.d2-2051738425 .background-color-B6{background-color:#F7F8FE;}
		.d2-2051738425 .background-color-AA2{background-color:#4A6FF3;}
		.d2-2051738425 .background-color-AA4{background-color:#EDF0FD;}
		.d2-2051738425 .background-color-AA5{background-color:#F7F8FE;}
		.d2-2051738425 .background-color-AB4{background-color:#EDF0FD;}
		.d2-2051738425 .background-color-AB5{background-color:#F7F8FE;}
		.d2-2051738425 .color-N1{color:#0A0F25;}
		.d2-2051738425 .color-N2{color:#676C7E;}
		.d2-2051738425 .color-N3{color:#9499AB;}
		.d2-2051738425 .color-N4{color:#CFD2DD;}
		.d2-2051738425 .color-N5{color:#DEE1EB;}
		.d2-2051738425 .color-N6{color:#EEF1F8;}
		.d2-2051738425 .color-N7{color:#FFFFFF;}
		.d2-2051738425 .color-B1{color:#0D32B2;}
		.d2-2051738425 .color-B2{color:#0D32B2;}
		.d2-2051738425 .color-B3{color:#E3E9FD;}
		.d2-2051738425 .color-B4{color:#E3E9FD;}
		.d2-2051738425 .color-B5{color:#EDF0FD;}
		.d2-2051738425 .color-B6{color:#F7F8FE;}
		.d2-2051738425 .color-AA2{color:#4A6FF3;}
		.d2-2051738425 .color-AA4{color:#EDF0FD;}
		.d2-2051738425 .color-AA5{color:#F7F8FE;}
		.d2-2051738425 .color-AB4{color:#EDF0FD;}
		.d2-2051738425 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-2051738425);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-2051738425);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-2051738425);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-2051738425);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-2051738425);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-2051738425);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-2051738425);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-2051738425);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-2051738425);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-2051738425);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-2051738425);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-2051738425);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-2051738425);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-2051738425);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-2051738425);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-2051738425);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-2051738425);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-2051738425);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}body.dark .d2-2051738425 .fill-N1{fill:#CDD6F4;}body.dark .d2-2051738425 .fill-N2{fill:#BAC2DE;}body.dark .d2-2051738425 .fill-N3{fill:#A6ADC8;}body.dark .d2-2051738425 .fill-N4{fill:#585B70;}body.dark .d2-2051738425 .fill-N5{fill:#45475A;}body.dark .d2-2051738425 .fill-N6{fill:#313244;}body.dark .d2-2051738425 .fill-N7{fill:#1E1E2E;}body.dark .d2-2051738425 .fill-B1{fill:#CBA6f7;}body.dark .d2-2051738425 .fill-B2{fill:#CBA6f7;}body.dark .d2-2051738425 .fill-B3{fill:#6C7086;}body.dark .d2-2051738425 .fill-B4{fill:#585B70;}body.dark .d2-2051738425 .fill-B5{fill:#45475A;}body.dark .d2-2051738425 .fill-B6{fill:#313244;}body.dark .d2-2051738425 .fill-AA2{fill:#f38BA8;}body.dark .d2-2051738425 .fill-AA4{fill:#45475A;}body.dark .d2-2051738425 .fill-AA5{fill:#313244;}body.dark .d2-2051738425 .fill-AB4{fill:#45475A;}body.dark .d2-2051738425 .fill-AB5{fill:#313244;}body.dark .d2-2051738425 .stroke-N1{stroke:#CDD6F4;}body.dark .d2-2051738425 .stroke-N2{stroke:#BAC2DE;}body.dark .d2-2051738425 .stroke-N3{stroke:#A6ADC8;}body.dark .d2-2051738425 .stroke-N4{stroke:#585B70;}body.dark .d2-2051738425 .stroke-N5{stroke:#45475A;}body.dark .d2-2051738425 .stroke-N6{stroke:#313244;}body.dark .d2-2051738425 .stroke-N7{stroke:#1E1E2E;}body.dark .d2-2051738425 .stroke-B1{stroke:#CBA6f7;}body.dark .d2-2051738425 .stroke-B2{stroke:#CBA6f7;}body.dark .d2-2051738425 .stroke-B3{stroke:#6C7086;}body.dark .d2-2051738425 .stroke-B4{stroke:#585B70;}body.dark .d2-2051738425 .stroke-B5{stroke:#45475A;}body.dark .d2-2051738425 .stroke-B6{stroke:#313244;}body.dark .d2-2051738425 .stroke-AA2{stroke:#f38BA8;}body.dark .d2-2051738425 .stroke-AA4{stroke:#45475A;}body.dark .d2-2051738425 .stroke-AA5{stroke:#313244;}body.dark .d2-2051738425 .stroke-AB4{stroke:#45475A;}body.dark .d2-2051738425 .stroke-AB5{stroke:#313244;}body.dark .d2-2051738425 .background-color-N1{background-color:#CDD6F4;}body.dark .d2-2051738425 .background-color-N2{background-color:#BAC2DE;}body.dark .d2-2051738425 .background-color-N3{background-color:#A6ADC8;}body.dark .d2-2051738425 .background-color-N4{background-color:#585B70;}body.dark .d2-2051738425 .background-color-N5{background-color:#45475A;}body.dark .d2-2051738425 .background-color-N6{background-color:#313244;}body.dark .d2-2051738425 .background-color-N7{background-color:#1E1E2E;}body.dark .d2-2051738425 .background-color-B1{background-color:#CBA6f7;}body.dark .d2-2051738425 .background-color-B2{background-color:#CBA6f7;}body.dark .d2-2051738425 .background-color-B3{background-color:#6C7086;}body.dark .d2-2051738425 .background-color-B4{background-color:#585B70;}body.dark .d2-2051738425 .background-color-B5{background-color:#45475A;}body.dark .d2-2051738425 .background-color-B6{background-color:#313244;}body.dark .d2-2051738425 .background-color-AA2{background-color:#f38BA8;}body.dark .d2-2051738425 .background-color-AA4{background-color:#45475A;}body.dark .d2-2051738425 .background-color-AA5{background-color:#313244;}body.dark .d2-2051738425 .background-color-AB4{background-color:#45475A;}body.dark .d2-2051738425 .background-color-AB5{background-color:#313244;}body.dark .d2-2051738425 .color-N1{color:#CDD6F4;}body.dark .d2-2051738425 .color-N2{color:#BAC2DE;}body.dark .d2-2051738425 .color-N3{color:#A6ADC8;}body.dark .d2-2051738425 .color-N4{color:#585B70;}body.dark .d2-2051738425 .color-N5{color:#45475A;}body.dark .d2-2051738425 .color-N6{color:#313244;}body.dark .d2-2051738425 .color-N7{color:#1E1E2E;}body.dark .d2-2051738425 .color-B1{color:#CBA6f7;}body.dark .d2-2051738425 .color-B2{color:#CBA6f7;}body.dark .d2-2051738425 .color-B3{color:#6C7086;}body.dark .d2-2051738425 .color-B4{color:#585B70;}body.dark .d2-2051738425 .color-B5{color:#45475A;}body.dark .d2-2051738425 .color-B6{color:#313244;}body.dark .d2-2051738425 .color-AA2{color:#f38BA8;}body.dark .d2-2051738425 .color-AA4{color:#45475A;}body.dark .d2-2051738425 .color-AA5{color:#313244;}body.dark .d2-2051738425 .color-AB4{color:#45475A;}body.dark .d2-2051738425 .color-AB5{color:#313244;}.appendix text.text{fill:#CDD6F4}.md{--color-fg-default:#CDD6F4;--color-fg-muted:#BAC2DE;--color-fg-subtle:#A6ADC8;--color-canvas-default:#1E1E2E;--color-canvas-subtle:#313244;--color-border-default:#CBA6f7;--color-border-muted:#CBA6f7;--color-neutral-muted:#313244;--color-accent-fg:#CBA6f7;--color-accent-emphasis:#CBA6f7;--color-attention-subtle:#BAC2DE;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-normal-d2-2051738425);mix-blend-mode:color-burn}.sketch-overlay-B2{fill:url(#streaks-normal-d2-2051738425);mix-blend-mode:color-burn}.sketch-overlay-B3{fill:url(#streaks-dark-d2-2051738425);mix-blend-mode:overlay}.sketch-overlay-B4{fill:url(#streaks-dark-d2-2051738425);mix-blend-mode:overlay}.sketch-overlay-B5{fill:url(#streaks-darker-d2-2051738425);mix-blend-mode:lighten}.sketch-overlay-B6{fill:url(#streaks-darker-d2-2051738425);mix-blend-mode:lighten}.sketch-overlay-AA2{fill:url(#streaks-normal-d2-2051738425);mix-blend-mode:color-burn}.sketch-overlay-AA4{fill:url(#streaks-darker-d2-2051738425);mix-blend-mode:lighten}.sketch-overlay-AA5{fill:url(#streaks-darker-d2-2051738425);mix-blend-mode:lighten}.sketch-overlay-AB4{fill:url(#streaks-darker-d2-2051738425);mix-blend-mode:lighten}.sketch-overlay-AB5{fill:url(#streaks-darker-d2-2051738425);mix-blend-mode:lighten}.sketch-overlay-N1{fill:url(#streaks-normal-d2-2051738425);mix-blend-mode:color-burn}.sketch-overlay-N2{fill:url(#streaks-normal-d2-2051738425);mix-blend-mode:color-burn}.sketch-overlay-N3{fill:url(#streaks-normal-d2-2051738425);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-dark-d2-2051738425);mix-blend-mode:overlay}.sketch-overlay-N5{fill:url(#streaks-darker-d2-2051738425);mix-blend-mode:lighten}.sketch-overlay-N6{fill:url(#streaks-darker-d2-2051738425);mix-blend-mode:lighten}.sketch-overlay-N7{fill:url(#streaks-darker-d2-2051738425);mix-blend-mode:lighten}.light-code{display: none}.dark-code{display: block}]]></style><g class="cmVwbw=="><g class="shape" ><rect x="10.000000" y="186.000000" width="466.000000" height="147.000000" fill="transparent" class=" stroke-B2" style="stroke-width:2;stroke-dasharray:6.000000,5.919384;" /></g><text x="243.000000" y="173.000000" class="text fill-N1" style="text-anchor:middle;font-size:28px">heartleo/blog · Private</text></g><g class="aHVnbw=="><g class="shape" ><path d="M 330 474 L 274 508 L 330 543 L 443 543 L 499 508 L 443 474 Z" class=" stroke-B1 fill-N5" style="stroke-width:2;" /></g><text x="386.500000" y="514.000000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">hugo --minify --gc</text></g><g class="cHVi"><g class="shape" ><path d="M 285 664 L 387 664 L 387 679 L 488 679 L 488 737 L 285 737 Z" class=" stroke-B1 fill-AA4" style="stroke-width:2;" /></g><text x="386.500000" y="713.300000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">public/ · 22 页 · 100ms</text></g><g class="Y2Y="><g class="shape" ><path d="M 312 887 C 312 888 311 889 309 889 C 290 890 275 901 275 915 C 275 930 292 942 313 942 H 457 C 480 942 498 929 498 914 C 498 899 481 887 459 886 C 458 886 456 885 456 884 C 451 869 429 858 403 858 C 386 858 371 863 361 870 C 360 871 358 871 357 871 C 353 870 349 870 345 870 C 327 870 313 877 312 887 Z" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="385.318500" y="921.516000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Cloudflare Workers</text></g><g class="ZG9t"><g class="shape" ><rect x="301.000000" y="1063.000000" width="172.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="387.000000" y="1101.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">blog.heartleo.dev</text></g><g class="cHVzaA=="><g class="shape" ><rect x="554.000000" y="0.000000" width="141.000000" height="66.000000" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="624.500000" y="38.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">git push main</text></g><g class="Z2E="><g class="shape" ><path d="M 575 225 L 526 259 L 575 294 L 674 294 L 723 259 L 674 225 Z" class=" stroke-B2 fill-N5" style="stroke-width:2;stroke-dasharray:6.000000,5.919384;" /></g><text x="624.500000" y="265.000000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">GitHub Actions</text></g><g class="cmVwby5zcmM="><g class="shape" ><path d="M 41 216 H 246 C 247 216 248 216 249 217 L 266 233 C 267 234 267 235 267 236 V 303 C 267 303 267 303 267 303 H 41 C 40 303 40 303 40 303 V 217 C 40 216 40 216 41 216 Z" class=" stroke-B1 fill-AB5" style="stroke-width:2;" /><path d="M 266 303 H 41 C 40 303 40 303 40 302 V 217 C 40 216 40 216 41 216 H 245 C 246 216 246 216 246 217 V 234 C 246 235 247 236 248 236 H 266 C 267 236 267 236 267 237 V 302 C 266 303 267 303 266 303 Z" class=" stroke-B1 fill-AB5" style="stroke-width:2;" /></g><text x="153.500000" y="265.000000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">index.md + 同目录图片</text></g><g class="cmVwby5jZmc="><g class="shape" ><path d="M 328 216 H 425 C 426 216 427 216 428 217 L 445 233 C 446 234 446 235 446 236 V 303 C 446 303 446 303 446 303 H 328 C 327 303 327 303 327 303 V 217 C 327 216 327 216 328 216 Z" class=" stroke-B1 fill-AB5" style="stroke-width:2;" /><path d="M 445 303 H 328 C 327 303 327 303 327 302 V 217 C 327 216 327 216 328 216 H 424 C 425 216 425 216 425 217 V 234 C 425 235 426 236 427 236 H 445 C 446 236 446 236 446 237 V 302 C 445 303 446 303 445 303 Z" class=" stroke-B1 fill-AB5" style="stroke-width:2;" /></g><text x="386.500000" y="265.000000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">hugo.toml</text></g><g class="KHJlcG8uc3JjIC0mZ3Q7IGh1Z28pWzBd"><marker id="mk-d2-2051738425-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 153.980001 304.999900 C 153.600006 343.000000 186.399994 427.200012 314.307360 480.462336" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-d2-2051738425-3488378134)" mask="url(#d2-2051738425)" /></g><g class="KHJlcG8uY2ZnIC0mZ3Q7IGh1Z28pWzBd"><path d="M 386.019999 304.999900 C 386.399994 343.000000 386.399994 425.600006 386.033056 470.000137" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-d2-2051738425-3488378134)" mask="url(#d2-2051738425)" /></g><g class="KHB1c2ggLSZndDsgZ2EpWzBd"><path d="M 624.500000 68.000000 C 624.500000 106.000000 624.400024 177.800003 624.033899 221.000144" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-d2-2051738425-3488378134)" mask="url(#d2-2051738425)" /></g><g class="KGdhIC0mZ3Q7IGh1Z28pWzBd"><path d="M 624.983050 295.999928 C 624.599976 341.200012 590.799988 427.200012 459.705507 480.493607" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-d2-2051738425-3488378134)" mask="url(#d2-2051738425)" /><text x="588.500000" y="434.000000" class="text-italic fill-N2" style="text-anchor:middle;font-size:16px">CI 跑同一条流水线</text></g><g class="KGh1Z28gLSZndDsgcHViKVswXQ=="><path d="M 386.983472 544.999932 C 386.600006 591.400024 386.600006 615.599976 386.966944 660.000137" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-d2-2051738425-3488378134)" mask="url(#d2-2051738425)" /><text x="387.000000" y="609.000000" class="text-italic fill-N2" style="text-anchor:middle;font-size:16px">图片转 webp 切三档</text></g><g class="KHB1YiAtJmd0OyBjZilbMF0="><path d="M 386.016528 738.999932 C 386.399994 785.400024 386.600006 810.000000 386.968002 856.000128" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-d2-2051738425-3488378134)" mask="url(#d2-2051738425)" /><text x="386.500000" y="804.000000" class="text-italic fill-N2" style="text-anchor:middle;font-size:16px">wrangler deploy</text></g><g class="KGNmIC0mZ3Q7IGRvbSlbMF0="><path d="M 386.983472 943.999932 C 386.600006 990.400024 386.500000 1014.700012 386.500000 1059.500000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-d2-2051738425-3488378134)" mask="url(#d2-2051738425)" /><text x="387.000000" y="1008.000000" class="text-italic fill-N2" style="text-anchor:middle;font-size:16px">routes + custom_domain</text></g><mask id="d2-2051738425" maskUnits="userSpaceOnUse" x="-15" y="-25" width="763" height="1179">
<rect x="-15" y="-25" width="763" height="1179" fill="white"></rect>
<rect x="511.000000" y="418.000000" width="155" height="21" fill="black"></rect>
<rect x="306.000000" y="593.000000" width="162" height="21" fill="black"></rect>
<rect x="331.000000" y="788.000000" width="111" height="21" fill="black"></rect>
<rect x="304.000000" y="992.000000" width="166" height="21" fill="black"></rect>
</mask></svg></svg>

  <figcaption>构建与部署。虚线框是私有仓，CI 跑的是和本地完全相同的那条流水线</figcaption>
</figure>
<hr>
<h2 id="依赖顺序总览">依赖顺序总览</h2>
<p>先看全貌，后面每节对应一行：</p>
<table>
	<thead>
			<tr>
					<th>步骤</th>
					<th>依赖</th>
					<th>做完之后你有</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>0. 域名托管到 Cloudflare</td>
					<td>无</td>
					<td>一个 Active 的 zone</td>
			</tr>
			<tr>
					<td>1. 装 Hugo extended</td>
					<td>无</td>
					<td>能构建</td>
			</tr>
			<tr>
					<td>2. 建站 + 主题 submodule</td>
					<td>1</td>
					<td>本地能跑起来</td>
			</tr>
			<tr>
					<td>3. 中文站两处配置</td>
					<td>2</td>
					<td>字数统计和搜索是对的</td>
			</tr>
			<tr>
					<td>4. 定下图片约定</td>
					<td>2</td>
					<td>可以开始写文章</td>
			</tr>
			<tr>
					<td>5. 图表用 D2（可选）</td>
					<td>2</td>
					<td>图能跟着深色模式变</td>
			</tr>
			<tr>
					<td>6. 两个 GitHub 仓 + giscus</td>
					<td>无</td>
					<td>评论可用</td>
			</tr>
			<tr>
					<td>7. 首次手动部署 + 绑域名</td>
					<td>0, 1-4</td>
					<td>线上可访问</td>
			</tr>
			<tr>
					<td>8. 交给 GitHub Actions</td>
					<td>6, 7</td>
					<td>push 即上线</td>
			</tr>
	</tbody>
</table>
<p>第 5 步可选，别的都不能跳。</p>
<p>第 7 步必须在第 8 步之前手动跑通一次。跳过它直接配 CI，出问题时分不清
是配置错了还是 CI 环境的事。</p>
<hr>
<h2 id="第-0-步域名托管到-cloudflare">第 0 步：域名托管到 Cloudflare</h2>
<p>Workers 绑自定义域名要求域名在同一个 Cloudflare 账号下，且 zone 状态是
Active。这一步生效要时间，所以放在最前面并行等着。</p>
<p>到 Cloudflare Dashboard 添加域名，把注册商那边的 NS 记录改成 Cloudflare
给的两条。几分钟到几小时不等，看注册商。</p>
<p>Dashboard 上该域名状态显示 Active 而不是 Pending，这一步就算过了。</p>
<h2 id="第-1-步装-hugo-extended">第 1 步：装 Hugo extended</h2>
<p>必须是 extended 版本。PaperMod 用了 SCSS，普通版 Hugo 构建直接失败。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="n">winget</span> <span class="n">install</span> <span class="n">Hugo</span><span class="p">.</span><span class="py">Hugo</span><span class="p">.</span><span class="py">Extended</span>
</span></span></code></pre></div><p>macOS 用 <code>brew install hugo</code>，brew 装的就是 extended。</p>
<p>跑 <code>hugo version</code>，输出里要能看到 <code>extended</code> 字样。</p>
<blockquote>
<p>别用 <code>npm i -D hugo-extended</code>。它在 postinstall 阶段去 GitHub Release
下载二进制再校验 hash，网络稍有波动就报 <code>Checksum mismatch</code>。
那个校验失败意味着下到的文件和上游发布的不是同一个，可能是连接被截断，
也可能是中间设备改写了响应，而它同时是供应链投毒会出现的信号。
遇到就换渠道装，<strong>不要去搜怎么跳过校验</strong>。</p>
</blockquote>
<p>Windows 上 winget 装完可能不在当前终端的 PATH 里，开个新终端再试。</p>
<h2 id="第-2-步建站主题用-submodule">第 2 步：建站，主题用 submodule</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">hugo new site blog --format toml
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> blog
</span></span><span class="line"><span class="cl">git init
</span></span><span class="line"><span class="cl">git submodule add -b master https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
</span></span></code></pre></div><p>用 submodule 而不是把主题文件直接拷进仓库，理由是升级主题只要
<code>git submodule update --remote</code>，不会和自己的改动混在一起。</p>
<p>代价是 CI 那边 checkout 必须显式拉子模块，第 8 步会用到：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">submodules</span><span class="p">:</span><span class="w"> </span><span class="l">recursive</span><span class="w">
</span></span></span></code></pre></div><p>漏了这行，CI 会在构建阶段报主题不存在。</p>
<p><code>hugo.toml</code> 最小可用配置：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">baseURL</span> <span class="p">=</span> <span class="s2">&#34;https://blog.example.com/&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">title</span> <span class="p">=</span> <span class="s2">&#34;站点标题&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">theme</span> <span class="p">=</span> <span class="s2">&#34;PaperMod&#34;</span>
</span></span></code></pre></div><p>跑 <code>hugo server -D</code>，<a href="http://localhost:1313">http://localhost:1313</a> 能打开就说明主题装对了。</p>
<h2 id="第-3-步中文站必须改的两处配置">第 3 步：中文站必须改的两处配置</h2>
<p>这两处不改，站能构建、能访问，但功能是坏的。</p>
<h3 id="字数统计和阅读时长">字数统计和阅读时长</h3>
<p>Hugo 默认按空格分词。一整段中文没有空格，会被算成 1 个词，结果每篇文章
都显示&quot;1 分钟 · 1 字&quot;。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">hasCJKLanguage</span> <span class="p">=</span> <span class="kc">true</span>
</span></span></code></pre></div><p>开了之后按字符统计 CJK。</p>
<h3 id="搜索">搜索</h3>
<p>PaperMod 的搜索基于 fuse.js，纯静态，索引来自首页多输出的一份 JSON。
先让 Hugo 生成这份 JSON：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputs</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="nx">home</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;HTML&#34;</span><span class="p">,</span> <span class="s2">&#34;RSS&#34;</span><span class="p">,</span> <span class="s2">&#34;JSON&#34;</span><span class="p">]</span>
</span></span></code></pre></div><p>然后把最小匹配长度设成 1。默认值对中文太大，中文一个字就是一个有意义的
搜索单位：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">params</span><span class="p">.</span><span class="nx">fuseOpts</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="nx">minMatchCharLength</span> <span class="p">=</span> <span class="mi">1</span>
</span></span></code></pre></div><p>还要建一个搜索页 <code>content/search.md</code>：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;搜索&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">layout</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;search&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">url</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;/search/&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span></code></pre></div><p>改完在搜索页输入一个中文词，能命中正文里含这个词的文章就对了。</p>
<p>顺带一提，文章的摘要别依赖 Hugo 自动截断，中文断句会很难看。
frontmatter 里显式写 <code>summary</code>。</p>
<h2 id="第-4-步定下图片约定">第 4 步：定下图片约定</h2>
<p>这个决定越晚改代价越大，所以在写第一篇之前定。</p>
<p>用 Hugo 的 page bundle，正文和图片放同一个文件夹：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">content/posts/how-i-built-my-blog/
</span></span><span class="line"><span class="cl">├── index.md          ← 正文，文件名必须是 index.md
</span></span><span class="line"><span class="cl">├── arch.png
</span></span><span class="line"><span class="cl">└── screenshot.png
</span></span></code></pre></div><p>URL 由文件夹名决定，是 <code>/posts/how-i-built-my-blog/</code>。</p>
<p>好处是删文章时图片跟着一起删干净，文件名只需在单篇内唯一，
不用在两棵目录树之间来回对照。</p>
<h3 id="写一个-img-shortcode">写一个 img shortcode</h3>
<p>直接用 markdown 的 <code>![]()</code> 也能显示图，但拿不到 Hugo 的图片处理。
<code>layouts/shortcodes/img.html</code> 里包一层，栅格图自动转 webp 并切多档：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$src</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="na">.Get</span><span class="w"> </span><span class="s">&#34;src&#34;</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$res</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="na">$.Page.Resources.GetMatch</span><span class="w"> </span><span class="nx">$src</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="nx">$srcset</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">slice</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">$w</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">(</span><span class="k">slice</span><span class="w"> </span><span class="nx">480</span><span class="w"> </span><span class="nx">960</span><span class="w"> </span><span class="nx">1440</span><span class="o">)</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">  <span class="cp">{{-</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">le</span><span class="w"> </span><span class="nx">$w</span><span class="w"> </span><span class="nx">$res</span><span class="na">.Width</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="nx">$v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">$res</span><span class="na">.Resize</span><span class="w"> </span><span class="o">(</span><span class="k">printf</span><span class="w"> </span><span class="s">&#34;%dx webp q82&#34;</span><span class="w"> </span><span class="nx">$w</span><span class="o">)</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="nx">$srcset</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">$srcset</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">append</span><span class="w"> </span><span class="o">(</span><span class="k">printf</span><span class="w"> </span><span class="s">&#34;%s %dw&#34;</span><span class="w"> </span><span class="nx">$v</span><span class="na">.RelPermalink</span><span class="w"> </span><span class="nx">$w</span><span class="o">)</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">  <span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">img</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="nx">$res</span><span class="na">.RelPermalink</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span>
</span></span><span class="line"><span class="cl">     <span class="na">srcset</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="nx">delimit</span><span class="w"> </span><span class="nx">$srcset</span><span class="w"> </span><span class="s">&#34;, &#34;</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span>
</span></span><span class="line"><span class="cl">     <span class="na">sizes</span><span class="o">=</span><span class="s">&#34;(max-width: 720px) 100vw, 720px&#34;</span>
</span></span><span class="line"><span class="cl">     <span class="na">width</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="nx">$res</span><span class="na">.Width</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span> <span class="na">height</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="nx">$res</span><span class="na">.Height</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span>
</span></span><span class="line"><span class="cl">     <span class="na">loading</span><span class="o">=</span><span class="s">&#34;lazy&#34;</span> <span class="na">decoding</span><span class="o">=</span><span class="s">&#34;async&#34;</span> <span class="na">alt</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Get</span><span class="w"> </span><span class="s">&#34;alt&#34;</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>一张 55 KB 的 PNG 截图，转出来是 4.4 / 12.7 / 21.6 KB 三档。
<code>width</code> 和 <code>height</code> 一定要输出，否则图片加载前占位为零，会有布局抖动。</p>
<p>文章里这样引用，<code>src</code> 直接写文件名：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">{{&lt; img src=&#34;arch.png&#34; alt=&#34;架构图&#34; caption=&#34;整体架构&#34; &gt;}}
</span></span></code></pre></div><p>再加一层保护：图片文件不存在时不输出任何标签，只在构建日志里 <code>warnf</code>
一条。这样先写好图位、后补图的时候，线上不会出现裂图。</p>
<h2 id="第-5-步图表用-d2">第 5 步：图表用 D2</h2>
<p>可选，但比截图省事得多，改一行文字不用重新截图。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="n">winget</span> <span class="n">install</span> <span class="n">Terrastruct</span><span class="p">.</span><span class="py">D2</span>
</span></span></code></pre></div><p><code>.d2</code> 和生成的 <code>.svg</code> 都放进文章目录，两者都提交：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">direction: down
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">repo: heartleo/blog · Private {
</span></span><span class="line"><span class="cl">  style.stroke-dash: 3
</span></span><span class="line"><span class="cl">  src: index.md + 同目录图片 { shape: page }
</span></span><span class="line"><span class="cl">}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">hugo: hugo --minify --gc { shape: hexagon }
</span></span><span class="line"><span class="cl">cf: Cloudflare Workers { shape: cloud }
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">repo.src -&gt; hugo
</span></span><span class="line"><span class="cl">hugo -&gt; cf: wrangler deploy
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">d2 --theme <span class="m">0</span> --dark-theme <span class="m">200</span> --pad <span class="m">24</span> pipeline.d2 pipeline.svg
</span></span></code></pre></div><p><strong>用 <code>direction: down</code>，不要 <code>direction: right</code>。</strong> 正文宽度只有 720px，
横向布局缩进去之后文字只剩几像素高，读不了。</p>
<p>选 D2 不选 Mermaid 的理由：Mermaid 要么在客户端加载约 1 MB JS，
要么预渲染时依赖无头 Chrome。D2 是单个 Go 二进制，出图约 130 毫秒，
读者侧零 JS。Mermaid 的优势是源码能在 GitHub 上直接预览，
仓库公开的话值得考虑。</p>
<p>生成之后有三处要后处理，写个脚本一起做掉：</p>
<table>
	<thead>
			<tr>
					<th>要做的事</th>
					<th>不做会怎样</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>从 <code>viewBox</code> 取值补上根 <code>&lt;svg&gt;</code> 的 <code>width</code> / <code>height</code></td>
					<td>d2 的根元素只有 <code>viewBox</code>，浏览器认为它没有固有尺寸，会把比正文窄的图拉满容器宽度，高度跟着涨</td>
			</tr>
			<tr>
					<td>把 <code>@media (prefers-color-scheme: dark)</code> 拆掉，每条规则前加 <code>body.dark</code></td>
					<td>d2 的深色跟随系统，PaperMod 的深色是给 <code>&lt;body&gt;</code> 加 <code>.dark</code> 类。手动切深色而系统是浅色时，页面变黑图还是白的</td>
			</tr>
			<tr>
					<td>删掉铺满画布的背景矩形</td>
					<td>d2 的深色底偏紫，和 PaperMod 的中性深色不同调，图会以一个色块浮在页面上</td>
			</tr>
	</tbody>
</table>
<p>第二条改完之后，SVG 必须内联进页面才有效。外链 <code>&lt;img&gt;</code> 里的 SVG 是独立
文档，看不到父页面的 <code>.dark</code> 类。在 shortcode 里判断 MIME 类型，是 svg
就输出 <code>$res.Content</code>。</p>
<h2 id="第-6-步两个-github-仓配-giscus">第 6 步：两个 GitHub 仓，配 giscus</h2>
<p>giscus 要求承载 Discussions 的仓库是 public。源码想私有的话，拆成两个。</p>
<figure class="post-img post-img--inline" role="img" aria-label="评论架构：私有源码仓构建出站点，站点文章底部加载 giscus，giscus 按 pathname 在公开的 blog-comments 仓建 discussion；源码仓和评论仓之间没有关联">
  <!-- 由 npm run diagrams 生成，不要手改。d2-src-sha256:3cc1e8ffb8511258 -->
<?xml version="1.0" encoding="utf-8"?><svg width="498" height="898" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-d2-version="v0.7.1-HEAD" preserveAspectRatio="xMinYMin meet" viewBox="0 0 498 898"><svg class="d2-914329994 d2-svg" width="498" height="898" viewBox="-25 -45 498 898"><style type="text/css"><![CDATA[
.d2-914329994 .text {
	font-family: "d2-914329994-font-regular";
}
@font-face {
	font-family: d2-914329994-font-regular;
	src: url("data:application/font-woff;base64,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");
}
.d2-914329994 .text-bold {
	font-family: "d2-914329994-font-bold";
}
@font-face {
	font-family: d2-914329994-font-bold;
	src: url("data:application/font-woff;base64,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");
}
.d2-914329994 .text-italic {
	font-family: "d2-914329994-font-italic";
}
@font-face {
	font-family: d2-914329994-font-italic;
	src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape {
  shape-rendering: geometricPrecision;
  stroke-linejoin: round;
}
.connection {
  stroke-linecap: round;
  stroke-linejoin: round;
}
.blend {
  mix-blend-mode: multiply;
  opacity: 0.5;
}

		.d2-914329994 .fill-N1{fill:#0A0F25;}
		.d2-914329994 .fill-N2{fill:#676C7E;}
		.d2-914329994 .fill-N3{fill:#9499AB;}
		.d2-914329994 .fill-N4{fill:#CFD2DD;}
		.d2-914329994 .fill-N5{fill:#DEE1EB;}
		.d2-914329994 .fill-N6{fill:#EEF1F8;}
		.d2-914329994 .fill-N7{fill:#FFFFFF;}
		.d2-914329994 .fill-B1{fill:#0D32B2;}
		.d2-914329994 .fill-B2{fill:#0D32B2;}
		.d2-914329994 .fill-B3{fill:#E3E9FD;}
		.d2-914329994 .fill-B4{fill:#E3E9FD;}
		.d2-914329994 .fill-B5{fill:#EDF0FD;}
		.d2-914329994 .fill-B6{fill:#F7F8FE;}
		.d2-914329994 .fill-AA2{fill:#4A6FF3;}
		.d2-914329994 .fill-AA4{fill:#EDF0FD;}
		.d2-914329994 .fill-AA5{fill:#F7F8FE;}
		.d2-914329994 .fill-AB4{fill:#EDF0FD;}
		.d2-914329994 .fill-AB5{fill:#F7F8FE;}
		.d2-914329994 .stroke-N1{stroke:#0A0F25;}
		.d2-914329994 .stroke-N2{stroke:#676C7E;}
		.d2-914329994 .stroke-N3{stroke:#9499AB;}
		.d2-914329994 .stroke-N4{stroke:#CFD2DD;}
		.d2-914329994 .stroke-N5{stroke:#DEE1EB;}
		.d2-914329994 .stroke-N6{stroke:#EEF1F8;}
		.d2-914329994 .stroke-N7{stroke:#FFFFFF;}
		.d2-914329994 .stroke-B1{stroke:#0D32B2;}
		.d2-914329994 .stroke-B2{stroke:#0D32B2;}
		.d2-914329994 .stroke-B3{stroke:#E3E9FD;}
		.d2-914329994 .stroke-B4{stroke:#E3E9FD;}
		.d2-914329994 .stroke-B5{stroke:#EDF0FD;}
		.d2-914329994 .stroke-B6{stroke:#F7F8FE;}
		.d2-914329994 .stroke-AA2{stroke:#4A6FF3;}
		.d2-914329994 .stroke-AA4{stroke:#EDF0FD;}
		.d2-914329994 .stroke-AA5{stroke:#F7F8FE;}
		.d2-914329994 .stroke-AB4{stroke:#EDF0FD;}
		.d2-914329994 .stroke-AB5{stroke:#F7F8FE;}
		.d2-914329994 .background-color-N1{background-color:#0A0F25;}
		.d2-914329994 .background-color-N2{background-color:#676C7E;}
		.d2-914329994 .background-color-N3{background-color:#9499AB;}
		.d2-914329994 .background-color-N4{background-color:#CFD2DD;}
		.d2-914329994 .background-color-N5{background-color:#DEE1EB;}
		.d2-914329994 .background-color-N6{background-color:#EEF1F8;}
		.d2-914329994 .background-color-N7{background-color:#FFFFFF;}
		.d2-914329994 .background-color-B1{background-color:#0D32B2;}
		.d2-914329994 .background-color-B2{background-color:#0D32B2;}
		.d2-914329994 .background-color-B3{background-color:#E3E9FD;}
		.d2-914329994 .background-color-B4{background-color:#E3E9FD;}
		.d2-914329994 .background-color-B5{background-color:#EDF0FD;}
		.d2-914329994 .background-color-B6{background-color:#F7F8FE;}
		.d2-914329994 .background-color-AA2{background-color:#4A6FF3;}
		.d2-914329994 .background-color-AA4{background-color:#EDF0FD;}
		.d2-914329994 .background-color-AA5{background-color:#F7F8FE;}
		.d2-914329994 .background-color-AB4{background-color:#EDF0FD;}
		.d2-914329994 .background-color-AB5{background-color:#F7F8FE;}
		.d2-914329994 .color-N1{color:#0A0F25;}
		.d2-914329994 .color-N2{color:#676C7E;}
		.d2-914329994 .color-N3{color:#9499AB;}
		.d2-914329994 .color-N4{color:#CFD2DD;}
		.d2-914329994 .color-N5{color:#DEE1EB;}
		.d2-914329994 .color-N6{color:#EEF1F8;}
		.d2-914329994 .color-N7{color:#FFFFFF;}
		.d2-914329994 .color-B1{color:#0D32B2;}
		.d2-914329994 .color-B2{color:#0D32B2;}
		.d2-914329994 .color-B3{color:#E3E9FD;}
		.d2-914329994 .color-B4{color:#E3E9FD;}
		.d2-914329994 .color-B5{color:#EDF0FD;}
		.d2-914329994 .color-B6{color:#F7F8FE;}
		.d2-914329994 .color-AA2{color:#4A6FF3;}
		.d2-914329994 .color-AA4{color:#EDF0FD;}
		.d2-914329994 .color-AA5{color:#F7F8FE;}
		.d2-914329994 .color-AB4{color:#EDF0FD;}
		.d2-914329994 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-914329994);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-914329994);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-914329994);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-914329994);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-914329994);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-914329994);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-914329994);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-914329994);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-914329994);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-914329994);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-914329994);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-914329994);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-914329994);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-914329994);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-914329994);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-914329994);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-914329994);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-914329994);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}body.dark .d2-914329994 .fill-N1{fill:#CDD6F4;}body.dark .d2-914329994 .fill-N2{fill:#BAC2DE;}body.dark .d2-914329994 .fill-N3{fill:#A6ADC8;}body.dark .d2-914329994 .fill-N4{fill:#585B70;}body.dark .d2-914329994 .fill-N5{fill:#45475A;}body.dark .d2-914329994 .fill-N6{fill:#313244;}body.dark .d2-914329994 .fill-N7{fill:#1E1E2E;}body.dark .d2-914329994 .fill-B1{fill:#CBA6f7;}body.dark .d2-914329994 .fill-B2{fill:#CBA6f7;}body.dark .d2-914329994 .fill-B3{fill:#6C7086;}body.dark .d2-914329994 .fill-B4{fill:#585B70;}body.dark .d2-914329994 .fill-B5{fill:#45475A;}body.dark .d2-914329994 .fill-B6{fill:#313244;}body.dark .d2-914329994 .fill-AA2{fill:#f38BA8;}body.dark .d2-914329994 .fill-AA4{fill:#45475A;}body.dark .d2-914329994 .fill-AA5{fill:#313244;}body.dark .d2-914329994 .fill-AB4{fill:#45475A;}body.dark .d2-914329994 .fill-AB5{fill:#313244;}body.dark .d2-914329994 .stroke-N1{stroke:#CDD6F4;}body.dark .d2-914329994 .stroke-N2{stroke:#BAC2DE;}body.dark .d2-914329994 .stroke-N3{stroke:#A6ADC8;}body.dark .d2-914329994 .stroke-N4{stroke:#585B70;}body.dark .d2-914329994 .stroke-N5{stroke:#45475A;}body.dark .d2-914329994 .stroke-N6{stroke:#313244;}body.dark .d2-914329994 .stroke-N7{stroke:#1E1E2E;}body.dark .d2-914329994 .stroke-B1{stroke:#CBA6f7;}body.dark .d2-914329994 .stroke-B2{stroke:#CBA6f7;}body.dark .d2-914329994 .stroke-B3{stroke:#6C7086;}body.dark .d2-914329994 .stroke-B4{stroke:#585B70;}body.dark .d2-914329994 .stroke-B5{stroke:#45475A;}body.dark .d2-914329994 .stroke-B6{stroke:#313244;}body.dark .d2-914329994 .stroke-AA2{stroke:#f38BA8;}body.dark .d2-914329994 .stroke-AA4{stroke:#45475A;}body.dark .d2-914329994 .stroke-AA5{stroke:#313244;}body.dark .d2-914329994 .stroke-AB4{stroke:#45475A;}body.dark .d2-914329994 .stroke-AB5{stroke:#313244;}body.dark .d2-914329994 .background-color-N1{background-color:#CDD6F4;}body.dark .d2-914329994 .background-color-N2{background-color:#BAC2DE;}body.dark .d2-914329994 .background-color-N3{background-color:#A6ADC8;}body.dark .d2-914329994 .background-color-N4{background-color:#585B70;}body.dark .d2-914329994 .background-color-N5{background-color:#45475A;}body.dark .d2-914329994 .background-color-N6{background-color:#313244;}body.dark .d2-914329994 .background-color-N7{background-color:#1E1E2E;}body.dark .d2-914329994 .background-color-B1{background-color:#CBA6f7;}body.dark .d2-914329994 .background-color-B2{background-color:#CBA6f7;}body.dark .d2-914329994 .background-color-B3{background-color:#6C7086;}body.dark .d2-914329994 .background-color-B4{background-color:#585B70;}body.dark .d2-914329994 .background-color-B5{background-color:#45475A;}body.dark .d2-914329994 .background-color-B6{background-color:#313244;}body.dark .d2-914329994 .background-color-AA2{background-color:#f38BA8;}body.dark .d2-914329994 .background-color-AA4{background-color:#45475A;}body.dark .d2-914329994 .background-color-AA5{background-color:#313244;}body.dark .d2-914329994 .background-color-AB4{background-color:#45475A;}body.dark .d2-914329994 .background-color-AB5{background-color:#313244;}body.dark .d2-914329994 .color-N1{color:#CDD6F4;}body.dark .d2-914329994 .color-N2{color:#BAC2DE;}body.dark .d2-914329994 .color-N3{color:#A6ADC8;}body.dark .d2-914329994 .color-N4{color:#585B70;}body.dark .d2-914329994 .color-N5{color:#45475A;}body.dark .d2-914329994 .color-N6{color:#313244;}body.dark .d2-914329994 .color-N7{color:#1E1E2E;}body.dark .d2-914329994 .color-B1{color:#CBA6f7;}body.dark .d2-914329994 .color-B2{color:#CBA6f7;}body.dark .d2-914329994 .color-B3{color:#6C7086;}body.dark .d2-914329994 .color-B4{color:#585B70;}body.dark .d2-914329994 .color-B5{color:#45475A;}body.dark .d2-914329994 .color-B6{color:#313244;}body.dark .d2-914329994 .color-AA2{color:#f38BA8;}body.dark .d2-914329994 .color-AA4{color:#45475A;}body.dark .d2-914329994 .color-AA5{color:#313244;}body.dark .d2-914329994 .color-AB4{color:#45475A;}body.dark .d2-914329994 .color-AB5{color:#313244;}.appendix text.text{fill:#CDD6F4}.md{--color-fg-default:#CDD6F4;--color-fg-muted:#BAC2DE;--color-fg-subtle:#A6ADC8;--color-canvas-default:#1E1E2E;--color-canvas-subtle:#313244;--color-border-default:#CBA6f7;--color-border-muted:#CBA6f7;--color-neutral-muted:#313244;--color-accent-fg:#CBA6f7;--color-accent-emphasis:#CBA6f7;--color-attention-subtle:#BAC2DE;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-normal-d2-914329994);mix-blend-mode:color-burn}.sketch-overlay-B2{fill:url(#streaks-normal-d2-914329994);mix-blend-mode:color-burn}.sketch-overlay-B3{fill:url(#streaks-dark-d2-914329994);mix-blend-mode:overlay}.sketch-overlay-B4{fill:url(#streaks-dark-d2-914329994);mix-blend-mode:overlay}.sketch-overlay-B5{fill:url(#streaks-darker-d2-914329994);mix-blend-mode:lighten}.sketch-overlay-B6{fill:url(#streaks-darker-d2-914329994);mix-blend-mode:lighten}.sketch-overlay-AA2{fill:url(#streaks-normal-d2-914329994);mix-blend-mode:color-burn}.sketch-overlay-AA4{fill:url(#streaks-darker-d2-914329994);mix-blend-mode:lighten}.sketch-overlay-AA5{fill:url(#streaks-darker-d2-914329994);mix-blend-mode:lighten}.sketch-overlay-AB4{fill:url(#streaks-darker-d2-914329994);mix-blend-mode:lighten}.sketch-overlay-AB5{fill:url(#streaks-darker-d2-914329994);mix-blend-mode:lighten}.sketch-overlay-N1{fill:url(#streaks-normal-d2-914329994);mix-blend-mode:color-burn}.sketch-overlay-N2{fill:url(#streaks-normal-d2-914329994);mix-blend-mode:color-burn}.sketch-overlay-N3{fill:url(#streaks-normal-d2-914329994);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-dark-d2-914329994);mix-blend-mode:overlay}.sketch-overlay-N5{fill:url(#streaks-darker-d2-914329994);mix-blend-mode:lighten}.sketch-overlay-N6{fill:url(#streaks-darker-d2-914329994);mix-blend-mode:lighten}.sketch-overlay-N7{fill:url(#streaks-darker-d2-914329994);mix-blend-mode:lighten}.light-code{display: none}.dark-code{display: block}]]></style><g class="c3Jj"><g class="shape" ><rect x="85.000000" y="20.000000" width="285.000000" height="126.000000" fill="transparent" class=" stroke-B2" style="stroke-width:2;stroke-dasharray:6.000000,5.919384;" /></g><text x="227.500000" y="7.000000" class="text fill-N1" style="text-anchor:middle;font-size:28px">heartleo/blog · Private</text></g><g class="c2l0ZQ=="><g class="shape" ><path d="M 51 316 C 51 317 50 318 48 318 C 30 319 16 330 16 344 C 16 359 32 371 52 371 H 188 C 209 371 227 358 227 343 C 227 328 211 316 190 315 C 189 315 187 314 187 313 C 182 298 161 287 137 287 C 121 287 106 292 97 299 C 96 300 94 300 93 300 C 89 299 86 299 81 299 C 66 299 52 306 51 316 Z" class=" stroke-B1 fill-N7" style="stroke-width:2;" /></g><text x="120.404500" y="350.516000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">blog.heartleo.dev</text></g><g class="Z2lzY3Vz"><g class="shape" ><path d="M 61 492 L 0 526 L 61 561 L 182 561 L 242 526 L 182 492 Z" class=" stroke-B1 fill-N5" style="stroke-width:2;" /></g><text x="121.000000" y="532.000000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">giscus · GitHub App</text></g><g class="Y210"><g class="shape" ><rect x="50.000000" y="702.000000" width="355.000000" height="126.000000" class=" stroke-B1 fill-B4" style="stroke-width:2;" /></g><text x="227.500000" y="689.000000" class="text fill-N1" style="text-anchor:middle;font-size:28px">heartleo/blog-comments · Public</text></g><g class="c3JjLmNvZGU="><g class="shape" ><rect x="115.000000" y="50.000000" width="225.000000" height="66.000000" class=" stroke-B1 fill-B5" style="stroke-width:2;" /></g><text x="227.500000" y="88.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">源码 / markdown / 配置</text></g><g class="Y210LmRpc2M="><g class="shape" ><rect x="80.000000" y="732.000000" width="295.000000" height="66.000000" class=" stroke-B1 fill-B5" style="stroke-width:2;" /></g><text x="227.500000" y="770.500000" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Discussions · Announcements 分类</text></g><g class="KHNyYy5jb2RlIC0mZ3Q7IHNpdGUpWzBd"><marker id="mk-d2-914329994-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 183.423951 117.231288 C 133.800003 156.000000 121.000000 239.000000 121.000000 285.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-d2-914329994-3488378134)" mask="url(#d2-914329994)" /><text x="128.500000" y="196.000000" class="text-italic fill-N2" style="text-anchor:middle;font-size:16px">hugo 构建后部署</text></g><g class="KHNpdGUgLSZndDsgZ2lzY3VzKVswXQ=="><path d="M 121.000000 373.000000 C 121.000000 419.399994 121.000000 443.600006 121.000000 488.000000" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-d2-914329994-3488378134)" mask="url(#d2-914329994)" /><text x="121.000000" y="437.000000" class="text-italic fill-N2" style="text-anchor:middle;font-size:16px">文章底部加载脚本</text></g><g class="KGdpc2N1cyAtJmd0OyBjbXQuZGlzYylbMF0="><path d="M 121.000000 563.000000 C 121.000000 609.400024 133.800003 692.000000 181.847903 729.537424" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-d2-914329994-3488378134)" mask="url(#d2-914329994)" /><text x="129.000000" y="664.000000" class="text-italic fill-N2" style="text-anchor:middle;font-size:16px">按 pathname 建 discussion</text></g><g class="KHNyYyAtJmd0OyBjbXQpWzBd"><marker id="mk-d2-914329994-1992324347" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" fill="#999" class="connection" stroke-width="2" /> </marker><path d="M 333.500000 147.500000 C 333.500000 210.300003 333.500000 247.000000 333.500000 277.750000 C 333.500000 308.500000 333.500000 349.500000 333.500000 380.250000 C 333.500000 411.000000 333.500000 450.500000 333.500000 479.000000 C 333.500000 507.500000 333.500000 629.500000 333.500000 657.500000" stroke="#999" fill="none" class="connection" style="stroke-width:2;stroke-dasharray:8.000000,7.892511;" marker-end="url(#mk-d2-914329994-1992324347)" mask="url(#d2-914329994)" /><text x="333.500000" y="409.000000" class="text-italic fill-N2" style="text-anchor:middle;font-size:16px">两者之间没有任何关联要求</text></g><mask id="d2-914329994" maskUnits="userSpaceOnUse" x="-25" y="-45" width="498" height="898">
<rect x="-25" y="-45" width="498" height="898" fill="white"></rect>
<rect x="60.000000" y="180.000000" width="137" height="21" fill="black"></rect>
<rect x="42.000000" y="421.000000" width="158" height="21" fill="black"></rect>
<rect x="35.000000" y="648.000000" width="188" height="21" fill="black"></rect>
<rect x="216.000000" y="393.000000" width="235" height="21" fill="black"></rect>
</mask></svg></svg>

  <figcaption>两个仓库之间那条虚线是重点：giscus 不要求它们有任何关系</figcaption>
</figure>
<table>
	<thead>
			<tr>
					<th>仓库</th>
					<th>可见性</th>
					<th>用途</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>blog</code></td>
					<td>Private</td>
					<td>源码、markdown、配置</td>
			</tr>
			<tr>
					<td><code>blog-comments</code></td>
					<td>Public</td>
					<td>只承载 Discussions，一行代码都没有</td>
			</tr>
	</tbody>
</table>
<p>giscus 指向的仓库和源码仓之间没有任何关联要求，它只是评论数据的存储
位置。唯一的可见差别是评论区&quot;查看讨论&quot;链接跳到 comments 仓。</p>
<h3 id="开-discussions">开 Discussions</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gh repo edit &lt;你的用户名&gt;/blog-comments --enable-discussions
</span></span></code></pre></div><p>也可以在 Settings → General → Features 里勾。</p>
<p>顺手把这个仓收紧，它只承载评论，另外三个功能开着等于白留入口：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gh repo edit &lt;你的用户名&gt;/blog-comments <span class="se">\
</span></span></span><span class="line"><span class="cl">  --enable-issues<span class="o">=</span><span class="nb">false</span> --enable-wiki<span class="o">=</span><span class="nb">false</span> --enable-projects<span class="o">=</span><span class="nb">false</span>
</span></span></code></pre></div><h3 id="分类直接用-announcements">分类直接用 Announcements</h3>
<p>开启 Discussions 时 GitHub 自动创建六个默认分类，其中 <code>Announcements</code>
的类型正好是 Announcement，只有仓库维护者能开新帖。这挡住了别人把
Discussions 当留言板刷，而 giscus 通过 App 权限建评论帖不受影响。</p>
<p>不用另建分类。</p>
<h3 id="装-giscus-app">装 giscus App</h3>
<p>打开 <a href="https://github.com/apps/giscus">https://github.com/apps/giscus</a>，点 Install，授权页选
<strong>Only select repositories</strong>，只勾 <code>blog-comments</code>。源码仓不要勾。</p>
<p>这是整套流程里唯一必须在浏览器点的一步，App 授权没有 CLI。</p>
<h3 id="取两个-id">取两个 ID</h3>
<p><code>repoId</code> 和 <code>categoryId</code> 可以在 <a href="https://giscus.app">https://giscus.app</a> 的配置生成器里拿，
也可以直接查：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gh api graphql -F <span class="nv">query</span><span class="o">=</span>@query.graphql
</span></span></code></pre></div><p><code>query.graphql</code>：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-graphql" data-lang="graphql"><span class="line"><span class="cl"><span class="kd">query</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="py">repository</span><span class="p">(</span><span class="py">owner</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;你的用户名&#34;</span><span class="p">,</span><span class="w"> </span><span class="nc">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;blog-comments&#34;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="py">id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="py">discussionCategories</span><span class="p">(</span><span class="py">first</span><span class="p">:</span><span class="w"> </span><span class="nc">25</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="py">nodes</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="py">id</span><span class="w"> </span><span class="py">name</span><span class="w"> </span><span class="py">isAnswerable</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>返回里 <code>repository.id</code> 是 <code>repoId</code>，<code>Announcements</code> 节点的 <code>id</code> 是
<code>categoryId</code>。</p>
<blockquote>
<p>PowerShell 里别用 <code>-f query='...'</code> 内联传 GraphQL，原生命令的参数解析
会把里面的引号搞坏。走文件。</p>
</blockquote>
<p>填进 <code>hugo.toml</code>：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">params</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="nx">comments</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="nx">params</span><span class="p">.</span><span class="nx">giscus</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="nx">repo</span> <span class="p">=</span> <span class="s2">&#34;你的用户名/blog-comments&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">repoId</span> <span class="p">=</span> <span class="s2">&#34;R_kgD...&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">category</span> <span class="p">=</span> <span class="s2">&#34;Announcements&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">categoryId</span> <span class="p">=</span> <span class="s2">&#34;DIC_kwD...&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">mapping</span> <span class="p">=</span> <span class="s2">&#34;pathname&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">strict</span> <span class="p">=</span> <span class="s2">&#34;1&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">lang</span> <span class="p">=</span> <span class="s2">&#34;zh-CN&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">theme</span> <span class="p">=</span> <span class="s2">&#34;preferred_color_scheme&#34;</span>
</span></span></code></pre></div><p>这两个 ID 不是密钥，可以放心提交，它们是前端脚本用的公开标识符，
本来就渲染在 HTML 里对所有访客可见。</p>
<p><code>mapping = &quot;pathname&quot;</code> 表示按页面路径匹配评论。这意味着<strong>改已发布文章的
URL 会让旧评论失联</strong>，改 slug 前要想清楚。<code>strict = &quot;1&quot;</code> 防止路径相近的
页面串评论。</p>
<p>PaperMod 在 <code>params.comments = true</code> 时会渲染 <code>layouts/partials/comments.html</code>，
把 giscus 的 script 放进去即可。</p>
<p>打开任意文章页，控制台会出现这条：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[giscus] Discussion not found. A new discussion will be created if a
</span></span><span class="line"><span class="cl">comment/reaction is submitted.
</span></span></code></pre></div><p>这是成功信号，不是错误。它说明 giscus 解析到了仓库和分类，只是这篇文章
还没人评论。App 没装或仓库填错的话，报的是
<code>giscus is not installed on this repository</code>，完全不同。</p>
<h2 id="第-7-步首次部署手动跑通">第 7 步：首次部署，手动跑通</h2>
<p><code>wrangler.jsonc</code>：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsonc" data-lang="jsonc"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;blog&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;compatibility_date&#34;</span><span class="p">:</span> <span class="s2">&#34;2026-07-20&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;assets&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;directory&#34;</span><span class="p">:</span> <span class="s2">&#34;./public&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;not_found_handling&#34;</span><span class="p">:</span> <span class="s2">&#34;404-page&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;routes&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="nt">&#34;pattern&#34;</span><span class="p">:</span> <span class="s2">&#34;blog.example.com&#34;</span><span class="p">,</span> <span class="nt">&#34;custom_domain&#34;</span><span class="p">:</span> <span class="kc">true</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>没有 <code>main</code> 字段，也就没有 Worker 脚本，纯静态资源托管。这一点后面会
影响 CI 里的 wrangler 版本选择。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="n">npx</span> <span class="n">wrangler</span> <span class="n">login</span>     <span class="c"># 浏览器 OAuth</span>
</span></span><span class="line"><span class="cl"><span class="n">hugo</span> <span class="p">-</span><span class="n">-minify</span> <span class="p">-</span><span class="n">-gc</span>
</span></span><span class="line"><span class="cl"><span class="n">npx</span> <span class="n">wrangler</span> <span class="n">deploy</span>
</span></span></code></pre></div><p>Worker 不需要提前在 Dashboard 建，首次 <code>wrangler deploy</code> 时自动创建，
名字取自配置里的 <code>name</code>。</p>
<p>自定义域名也不用去 DNS 面板加记录。<code>custom_domain: true</code> 会自动建 DNS
记录并签发证书。手动加 CNAME 反而冲突。</p>
<p>证书签发要一两分钟，期间访问是 <code>ERR_CONNECTION_CLOSED</code>，属于正常现象。</p>
<blockquote>
<p>配置里一旦有 <code>routes</code> 而没写 <code>workers_dev</code>，wrangler 会默认关掉
<code>*.workers.dev</code> 路由。这个默认值是合理的，同一份内容挂两个域名对 SEO
不利。想保留就显式加 <code>&quot;workers_dev&quot;: true</code>。</p>
</blockquote>
<p>Workers 静态资源的限制：免费版每个版本 20,000 个文件，付费版 100,000，
单文件都是 25 MiB。普通博客离这些数字很远。</p>
<p>等证书好了之后 <code>curl -I https://你的域名</code> 返回 200，这一步就完成了。</p>
<h2 id="第-8-步交给-github-actions">第 8 步：交给 GitHub Actions</h2>
<h3 id="建-api-token">建 API Token</h3>
<p>到 <a href="https://dash.cloudflare.com/profile/api-tokens">https://dash.cloudflare.com/profile/api-tokens</a>，创建自定义令牌。
不要用模板，模板给的权限比需要的大。</p>
<p>需要四条权限：</p>
<table>
	<thead>
			<tr>
					<th>类型</th>
					<th>资源</th>
					<th>权限</th>
					<th>为什么</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>帐户</td>
					<td>Workers 脚本</td>
					<td>编辑</td>
					<td>上传 Worker 和静态资源</td>
			</tr>
			<tr>
					<td>帐户</td>
					<td>帐户设置</td>
					<td>读取</td>
					<td>解析 Account ID</td>
			</tr>
			<tr>
					<td>区域</td>
					<td>Workers 路由</td>
					<td>编辑</td>
					<td>绑定自定义域名要动 zone 路由</td>
			</tr>
			<tr>
					<td>区域</td>
					<td>区域</td>
					<td>读取</td>
					<td>解析 zone ID</td>
			</tr>
	</tbody>
</table>
<p>后两条容易漏。<code>routes</code> 加 <code>custom_domain</code> 是<strong>区域级操作</strong>，只给帐户级
权限的话部署会报 <code>Authentication error [code: 10000]</code>，
请求路径里能看到 <code>/zones/</code>。</p>
<p>帐户资源限定到自己的账号，区域资源限定到那一个域名。
<strong>不要用 Global API Key</strong>，它覆盖整个账号的所有服务。</p>
<p>权限不够时可以编辑现有 token 补上，token 值不变，GitHub Secret 不用重设。</p>
<h3 id="存进-secrets">存进 Secrets</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gh secret <span class="nb">set</span> CLOUDFLARE_API_TOKEN --repo &lt;你的用户名&gt;/blog
</span></span><span class="line"><span class="cl">gh secret <span class="nb">set</span> CLOUDFLARE_ACCOUNT_ID --repo &lt;你的用户名&gt;/blog --body <span class="s2">&#34;&lt;account id&gt;&#34;</span>
</span></span></code></pre></div><p>token 那条<strong>不要带 <code>--body</code></strong>。不带时 gh 会提示交互式粘贴，输入不回显、
不进 shell history。写成参数就留在历史记录里了。</p>
<p>Account ID 不是密钥，<code>npx wrangler whoami</code> 随时能打印。</p>
<h3 id="workflow">workflow</h3>
<p><code>.github/workflows/deploy.yml</code>：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">push</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">branches</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">main]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">workflow_dispatch</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">concurrency</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">group</span><span class="p">:</span><span class="w"> </span><span class="l">deploy-${{ github.ref }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cancel-in-progress</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">deploy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">timeout-minutes</span><span class="p">:</span><span class="w"> </span><span class="m">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">submodules</span><span class="p">:</span><span class="w"> </span><span class="l">recursive</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">fetch-depth</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/setup-node@v4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">node-version</span><span class="p">:</span><span class="w"> </span><span class="m">22</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Setup Hugo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">peaceiris/actions-hugo@v3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">hugo-version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;0.164.0&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">extended</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">hugo --minify --gc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">cloudflare/wrangler-action@v3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">apiToken</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.CLOUDFLARE_API_TOKEN }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">accountId</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.CLOUDFLARE_ACCOUNT_ID }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">wranglerVersion</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;4.111.0&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="l">deploy</span><span class="w">
</span></span></span></code></pre></div><p>里面有三处不能省：</p>
<table>
	<thead>
			<tr>
					<th>配置</th>
					<th>为什么</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>wranglerVersion: &quot;4.111.0&quot;</code></td>
					<td><code>wrangler-action@v3</code> 默认装 wrangler 3.90.0，那个版本不认识 <code>assets</code> 配置，会去找 <code>main</code> 然后报 <code>Missing entry-point</code>。本地用 4.x 的话完全复现不出来</td>
			</tr>
			<tr>
					<td><code>hugo-version: &quot;0.164.0&quot;</code></td>
					<td>用 <code>latest</code> 的话上游一发新版就可能在 CI 里炸，同样在本地复现不出来。升级时本地和这里一起改</td>
			</tr>
			<tr>
					<td><code>cancel-in-progress: false</code></td>
					<td>部署中途被取消可能留下上传了一半的静态资源。排队等前一次跑完更安全，反正部署只要几十秒</td>
			</tr>
	</tbody>
</table>
<p><code>workflow_dispatch</code> 让 Actions 页面出现 Run workflow 按钮，不改代码也能
重新部署。</p>
<p>push 一次，Actions 变绿且线上内容更新，整套就通了。</p>
<p>私有仓 Actions 免费额度每月 2000 分钟，这个站构建一次约 30 秒。
公开仓不限量。</p>
<hr>
<h2 id="发布前的验证清单">发布前的验证清单</h2>
<table>
	<thead>
			<tr>
					<th>检查</th>
					<th>方法</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>阅读时长正常</td>
					<td>首页条目显示的分钟数不是全部为 1</td>
			</tr>
			<tr>
					<td>中文搜索能命中</td>
					<td>搜索页输入一个中文词</td>
			</tr>
			<tr>
					<td>评论区加载</td>
					<td>控制台出现 <code>Discussion not found</code> 那条</td>
			</tr>
			<tr>
					<td>深色模式</td>
					<td>手动切换主题，图片和图表跟着变</td>
			</tr>
			<tr>
					<td>自定义域名</td>
					<td><code>curl -I https://你的域名</code> 返回 200</td>
			</tr>
			<tr>
					<td>CI 通过</td>
					<td>push 后 Actions 变绿</td>
			</tr>
	</tbody>
</table>
<h2 id="排查表">排查表</h2>
<table>
	<thead>
			<tr>
					<th>症状</th>
					<th>原因和处理</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>文章写完了站上看不到</td>
					<td><code>draft: true</code>，或者 <code>date</code> 是未来时间被 <code>buildFuture = false</code> 过滤了</td>
			</tr>
			<tr>
					<td>阅读时长全是 1 分钟</td>
					<td><code>hasCJKLanguage = true</code> 没加</td>
			</tr>
			<tr>
					<td>中文搜不到</td>
					<td><code>fuseOpts.minMatchCharLength</code> 没设成 1，或 <code>outputs.home</code> 里没加 <code>JSON</code></td>
			</tr>
			<tr>
					<td>shortcode 没执行也不报错</td>
					<td>写成了 <code>{{&lt; ... &gt;}}</code>。那是转义写法，用来展示语法本身</td>
			</tr>
			<tr>
					<td>图片不显示</td>
					<td>构建日志里找 <code>WARN img:</code>，它会打出期望的完整路径</td>
			</tr>
			<tr>
					<td>CI 报 <code>Missing entry-point</code></td>
					<td>wrangler 版本太老不认识 <code>assets</code>，<code>wranglerVersion</code> 没显式指定</td>
			</tr>
			<tr>
					<td>CI 报 <code>Authentication error [code: 10000]</code></td>
					<td>API token 缺区域级权限，补齐上面那张表里的四条</td>
			</tr>
			<tr>
					<td>本地搜索结果重复</td>
					<td><code>hugo server</code> 内存增量重建的产物，不是 bug。干净构建后查 <code>public/index.json</code> 可确认</td>
			</tr>
			<tr>
					<td><code>hugo</code> 命令找不到</td>
					<td>winget 装的路径没进当前终端的 PATH，开个新终端</td>
			</tr>
	</tbody>
</table>
<h2 id="几条经验">几条经验</h2>
<p>先手动跑通再自动化。第 7 步和第 8 步分开做，出问题时能二分。</p>
<p>CI 里用到的版本全部钉死。本地和 CI 版本不同导致的问题，在本地永远复现
不出来，排查成本最高。上面那两个版本号就是这么来的。</p>
<p>看到 checksum mismatch 别绕过去，那是校验在正常工作。</p>
<p>哪一步卡住了，评论区说一声。</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
