为什么选这套方案

  • Hugo:由 Go 编写,构建速度极快。Hexo 依赖 Node.js,Jekyll 依赖 Ruby,版本一换就瘫痪;Hugo 是单一二进制文件,没有这个问题。
  • GitHub:内容以 Markdown 存储,天然版本控制。
  • Cloudflare Pages:免费,全球 CDN,自动监听 GitHub 推送并构建,国内可访问。

整个方案运营成本接近于零。


用 npm 管理 Hugo 版本

Hugo 是独立二进制文件,通常需要手动安装。为了可移植性,使用 hugo-bin 这个 npm 包代替:

npm install hugo-bin --save-dev

npm install 时会自动下载当前系统对应的 Hugo Extended 二进制文件到 node_modules,Hugo 版本锁定在 package.json,换电脑也不需要重新配置环境:

{
  "scripts": {
    "dev":   "hugo server",
    "build": "hugo --gc --minify",
    "new":   "hugo new"
  },
  "hugo-bin": {
    "buildTags": "extended",
    "version": "0.147.0"
  }
}

日常命令:

命令作用
npm run dev启动本地预览(http://localhost:1313,热重载)
npm run new -- posts/title.md新建文章,自动套用模板
npm run build构建生产环境静态文件到 public/

主题:PaperMod via Git Submodule

去官网 https://themes.gohugo.io/ 找自己喜欢的主题。

PaperMod 以 Git Submodule 方式引入,不直接复制主题文件。好处是主题有独立版本历史,升级时只需更新 submodule:

git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod

克隆仓库时需加 --recursive 参数一并拉取主题:

git clone --recursive <仓库地址>

关键配置:hugo.toml

baseURL

baseURL = 'https://your-domain.pages.dev/'

决定所有内部链接的前缀。留着默认的 example.org 不改,是最常见的坑——站点能访问,但导航栏所有链接都跳到错误域名。

搜索:必须开启 JSON 输出

PaperMod 的站内搜索是纯前端实现,依赖构建时生成的 index.json

[outputs]
home = ["HTML", "RSS", "JSON"]

少了 "JSON",搜索框出现但无结果。

首页:ProfileMode

[params.profileMode]
enabled = true
title    = "Hi, I'm xxx 👋"
subtitle = "Writing about tech, tools, and the occasional life update."

enabled = false 则首页直接变为文章列表。

导航菜单

菜单顺序由 weight 控制,数值越小越靠前:

[[menu.main]]
identifier = "posts"
name       = "Posts"
url        = "/posts/"
weight     = 10

[[menu.main]]
identifier = "categories"
name       = "Categories"
url        = "/categories/"
weight     = 20

[[menu.main]]
identifier = "tags"
name       = "Tags"
url        = "/tags/"
weight     = 30

[[menu.main]]
identifier = "search"
name       = "Search"
url        = "/search/"
weight     = 40

部署:Cloudflare Pages

  1. 登录 Cloudflare 控制台Workers & PagesCreate applicationPagesConnect to Git
  2. 选择博客仓库,Build settings 配置:
    • Build commandhugo --gc --minify
    • Build output directorypublic
  3. Environment variables 中添加:HUGO_VERSION = 0.147.0
  4. Save and Deploy

必须设置 HUGO_VERSION,否则 Cloudflare 使用极旧的默认版本,PaperMod 样式会编译失败。

部署完成后获得免费的 *.pages.dev 域名,也可在后台 Custom domains 绑定自己的域名。


主题定制:侧边 TOC

PaperMod 默认 TOC 是文章顶部的可折叠块。通过 Hugo 布局覆盖机制,不修改主题文件,将 TOC 改为页面侧面的固定侧边栏。

新增两个文件:

  • layouts/single.html:复制自主题,将 TOC 移出 <article>,放到独立 <aside>,使用 Hugo 原生 .TableOfContents
  • assets/css/extended/toc-sidebar.css:用 position: fixed 定位在正文侧面空白处
.toc-sidebar {
    position: fixed;
    top: calc(60px + 2.5rem);
    left: calc(50% + 360px + 40px); /* 正文右边缘 + 间距 */
    width: 200px;
}

视口宽度 ≥ 1200px 时显示,更窄时自动隐藏。主题升级时对比 themes/PaperMod/layouts/single.html 与自定义版本差异并手动同步。


渲染 Mermaid 图表

PaperMod 和 Hugo 都没有内置 Mermaid 支持,通过自定义短代码实现,两步完成:

1. 新建短代码 layouts/shortcodes/mermaid.html

<pre class="mermaid">{{ .Inner }}</pre>

2. 在文章页引入 Mermaid CDN,在 layouts/single.html 末尾加入:

<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
<script>
  mermaid.initialize({ startOnLoad: true, theme: "default" });
</script>

用 {{}} 分别包裹< mermaid > 和 < /mermaid > 即可渲染,此处没有包裹是因为会强制渲染

< mermaid > 
graph LR
    A[开始] --> B{判断}
    B -->|是| C[结束]
    B -->|否| A
< /mermaid >
graph LR
    A[开始] --> B{判断}
    B -->|是| C[结束]
    B -->|否| A

文件目录结构

blog/
├── content/
│   ├── posts/                  # 博客文章(Markdown)
│   │   └── 2026/               # 文章多时按年份建子目录(URL 自动含路径)
│   └── about.md                # 独立页面(如"关于")
├── static/
│   └── images/                 # 图片等静态资源
│                               # Markdown 中用绝对路径引用:/images/xxx.png
├── archetypes/
│   └── default.md              # 新文章默认模板(含完整 Front Matter 字段)
├── assets/
│   └── css/extended/           # 自定义 CSS(自动加载,覆盖主题样式)
│       └── toc-sidebar.css
├── layouts/
│   ├── single.html             # 覆盖主题文章页模板(不修改主题文件本身)
│   └── shortcodes/
│       └── mermaid.html        # Mermaid 图表短代码
├── themes/
│   └── PaperMod/               # 主题(Git Submodule,不直接修改)
└── hugo.toml                   # 全局配置

分类 (Categories) 与标签 (Tags)

两者都是文章的筛选维度,区别在粒度:

CategoriesTags
定位文章所属领域(大方向)文章涉及的具体知识点
粒度粗,数量少且稳定细,随内容自由增长
建议数量/篇1~2 个3~5 个

在 Front Matter 中定义:

categories = ["后端开发"]
tags       = ["Docker", "微服务", "部署"]

Hugo 会自动生成 /categories//tags/ 聚合页,无需手动创建。


写作工作流

flowchart LR
    A[新建文章] --> B[本地预览]
    B --> C[写作]
    C --> D[发布]
    D --> E[推送]
    E --> F[Cloudflare 自动部署]
  1. npm run new -- posts/your-title.md
  2. npm run dev,打开 http://localhost:1313 实时预览
  3. 写完后将 draft = true 改为 draft = false
  4. git add . && git commit -m "post: 文章标题" && git push
  5. Cloudflare 自动构建,约 1 分钟后线上更新

跨设备 Clone 后初始化

博客仓库在另一台设备上 clone 后,需要两步初始化:

# 1. 安装 Hugo(hugo-bin)
npm install

# 2. 拉取 PaperMod 主题(Git Submodule)
git submodule update --init --recursive

如果 clone 时用了 git clone --recursive,第 2 步可跳过。但 npm install 仍然必须执行。