浮生半刻

August 29, 2026 · View on GitHub

基于 Nuxt Clarity / blog-v3 视觉风格的 Hugo Extended 静态博客。

功能一览

能力说明
主题亮/暗/跟随系统
文章/archives/{id}
精选首页轮播,recommend / featured
说说/essay,data/talks.json
相册/album,瀑布流 + 异步加载 + 灯箱
友链/link,data/friends.json 可完全自定义
更新日志右侧栏,data/changelog.json
搜索Pagefind + JSON 回退
评论Twikoo / Waline / Artalk / Valine
页脚data/footer.toml 分组导航 + HTML 版权
图标侧栏 / iconNav / 页脚统一 icon 名,内置 40+ 可扩展
代码注入head / 内容页 head / 页脚脚本

1. 快速开始

cd hugo-clarity
npm install
npm run dev       # http://127.0.0.1:1314/
npm run build     # 生产 + Pagefind
npm run preview

部署上传整个 public/(必须含 pagefind/)。
环境:Hugo Extended ≥ 0.158(推荐 0.164+)、Node ≥ 18。
平台部署见 §5:Cloudflare Pages / Vercel / 腾讯云 EdgeOne;CI 用 npm run build:ci。


2. 目录结构

hugo-clarity/
├── hugo.toml
├── vercel.json             # Vercel:无尾斜杠 + 缓存头
├── edgeone.json            # EdgeOne:缓存与安全响应头
├── package.json            # build / build:ci / pagefind
├── content/
│   ├── posts/              # 文章
│   ├── album/              # 相册
│   ├── essay.md            # 说说页
│   ├── link.md             # 友链页(文案/申请说明)
│   └── about.md
├── data/
│   ├── friends.json        # 友链数据(自定义重点)
│   ├── talks.json          # 说说
│   ├── changelog.json      # 更新日志
│   └── footer.toml         # 页脚自定义(导航/版权/备案)
├── deploy/
│   └── nginx-no-trailing-slash.conf
├── scripts/
│   ├── build-ci.js         # CI:ensure-hugo + build
│   ├── ensure-hugo.js      # CI 下载 Hugo Extended
│   ├── gen-redirects.js    # 生成 public/_redirects
│   ├── export-friends.py
│   └── import-posts.py
├── static/_headers         # Cloudflare / 兼容主机缓存头
├── themes/clarity/
└── public/                 # 构建产物(整包上传)

3. 配置说明

3.1 上线必改(hugo.toml)

baseURL = "https://blog.study996.cn/"
title = "浮生半刻"
defaultContentLanguage = "zh-cn"   # 必须全小写
locale = "zh-cn"                 # Hugo >= 0.158,替代 languageCode

3.1.1 路由与尾部斜杠(重要)

目标 URL 形态(与原 Nuxt 一致,不要末尾 /):

页面正确错误
友链/link/link/
说说/essay/essay/
文章/archives/XGNl27pq/archives/XGNl27pq/
归档/archive/archive/
标签/tags、/tags/hugo带尾 /
关于/about/about/
相册/album/album/

为什么以前“去不掉”?

Hugo 默认把页面写成目录:public/essay/index.html。
自带的 hugo server 会把地址强制成 /essay/(自动加尾斜杠)。
只改 HTML 里的链接不够——地址栏仍会被开发服务器改回带 /。

正确本地开发方式

npm run dev
# 等价于:Hugo 监听构建 public/ + clean-server(无尾斜杠)
# 打开 http://127.0.0.1:1314/link
#      http://127.0.0.1:1314/archives/XGNl27pq
#      http://127.0.0.1:1314/essay
命令说明
npm run dev推荐:无尾斜杠开发
npm run serve仅静态预览已构建的 public/
npm run preview生产构建 + clean-server
npm run dev:hugo-server官方 Hugo server(会强制尾斜杠,仅排障用)

站点配置

[params]
  # false(默认):输出 /link 、/archives/id
  # true          :输出 /link/、/archives/id/
  trailingSlash = false

主题会处理:

  1. 站内链接(导航、文章卡、分页、标签…)
  2. canonical / og:url
  3. sitemap.xml / RSS
  4. 搜索索引与评论 path
  5. 构建后生成 public/_redirects:/path/ → 301 /path(Cloudflare Pages / Netlify)
  6. 页面内联脚本:若地址栏仍带 /,会 history.replaceState 去掉

内容 front matter

文章:

url: /archives/你的id

单页(示例):

# content/link.md / essay.md / about.md
url: /link    # 不要写成 /link/

导航菜单(hugo.toml)同样写无尾斜杠:url = "/essay"。

上线部署

  1. npm run build(本地已有 Hugo)或 npm run build:ci(Vercel / EdgeOne 等 CI)
  2. 上传/发布整个 public/(含 pagefind/、_redirects、_headers)
  3. 主机若强制加尾斜杠,请关闭;自建可用 deploy/nginx-no-trailing-slash.conf
  4. 平台细则见 §5.2 Cloudflare Pages、§5.3 Vercel、§5.4 EdgeOne
  5. Cloudflare / Netlify / 部分 EdgeOne 读取 _redirects;Vercel 使用根目录 vercel.json 的 trailingSlash = false

3.2 更新日志 → data/changelog.json

[
  { "date": "2026-07-25", "text": "迁移至 Hugo Clarity…" },
  { "date": "2019-07-19", "text": "发布第一篇文章" }
]

3.3 技术信息 / 社区(hugo.toml)

[params.tech]
  title = "技术信息"
  [[params.tech.items]]
    label = "主题"
    value = "Clarity (Hugo)"

[params.commGroup]
  name = "纸网接入点"
  qq = "169994096"

3.4.1 归档页

路径:/archive。视觉对齐原 Nuxt Clarity(大号描边年份、年龄、字数/篇数、条目 hover 渐变描边)。

[params.archive]
  birthYear = 2000        # 「N岁」= 文章年份 - birthYear
  allowAscending = true   # 允许正序

分类颜色沿用 [params.categories]。前端可筛选分类、切换创建/更新日期与升降序。

3.4 精选文章

[params.featured]
  enable = true
  max = 12

文章 front matter:recommend: 100 或 featured: true,并提供 image。

3.4.2 首页排序与分类

首页支持与原 Nuxt 相同的列表控制(客户端增强,无 JS 时仍为默认「创建日期倒序」分页):

操作说明URL 参数
分类按文章首个 categories 筛选?category=日常&生活
排序字段在「创建日期 / 更新日期」间切换?sort=updated
升降序默认关闭(对齐原站);[params.home].allowAscending = true 开启?asc=true
分页筛选后客户端分页?page=2

精选轮播仅在「第 1 页 + 全部分类」时显示。

3.5 说说 → data/talks.json

[
  {
    "text": "今天…",
    "date": "2026-07-25 18:30",
    "images": ["https://a.jpg"],
    "tags": ["日常"],
    "location": "湖北"
  }
]

3.6 相册 → content/album/*.md

列表与详情均为 瀑布流(多列 masonry):首屏 SSR,其余 下滚异步加载(IntersectionObserver + loading=lazy)。

[params.album]
  enable = true
  columns = 3           # 列表列数 1-4(小屏自动减少)
  initial = 6           # 列表首屏数量
  batchSize = 9         # 列表每批异步追加
  photoColumns = 3      # 详情页列数
  photoInitial = 4      # 详情首屏图片
  photoBatchSize = 6    # 详情每批异步追加

相册 Markdown 示例:

---
title: "春游"
cover: "https://cover.jpg"
images:
  - "https://1.jpg"
  - src: "https://2.jpg"
    caption: "说明"
---

3.7 友链 → data/friends.json(如何自定义)

友链页地址:/link(无尾部斜杠)。

文件管什么是否需要改模板
data/friends.json自己的博客信息、分组、每一条友链卡片否,改 JSON 即可
content/link.md页面标题、简介、申请说明正文、右侧栏组件否
主题模板布局/样式一般不改

日常自定义只改 friends.json + 可选改 link.md。

3.7.1 先搞清页面长什么样

打开 /link 从上到下是:

  1. 标题区:来自 content/link.md 的 title / description,以及 JSON 统计出的「X 组 · Y 个站点」
  2. 分组卡片区:来自 friends.json 的 groups
  3. 我的博客信息:来自 friends.json 的 my(没有 my 就不显示)
  4. 申请说明:来自 content/link.md 正文 Markdown

3.7.2 JSON 总结构

{
  "randomInGroup": true,
  "my": { },
  "groups": [
    {
      "name": "分组名",
      "desc": "分组说明(可选)",
      "entries": [ { } ]
    }
  ]
}
顶层字段必填作用
randomInGroup否true:进入页面时组内随机一次;点击分组大标题可再次打乱;按住 Ctrl/Cmd/Shift/Alt 点击恢复原序。false:固定 JSON 顺序
my建议「我的博客信息」区块;删掉整个 my 就不显示
groups是友链分组;可 1 组也可多组;空数组则只有申请说明

3.7.3 字段与界面对照(自定义时看这个)

卡片上实际显示的:

字段卡片效果
author主标题(没有则回退 title / sitenick / link)
sitenick副标题(灰色小字)
avatar圆形头像;没有则显示首字占位
link必填。点击跳转;没有 link 不会进列表
feed有值不显示铃铛;空/"" 时头像右下角显示「无订阅源」
desc鼠标悬停 title 提示

「我的博客信息」额外显示: author / sitenick / title / desc / link / avatar / feed / date / archs / comment(有值才显示)。

写在 JSON 里但列表卡片不展示的: icon、archs、date、comment(方便自己备注或以后扩展)。

隐藏规则:

  • error 为非空字符串 → 该条不显示(可当失效归档,不必真删除)
  • error 用 "" 或省略 → 正常显示
  • 没有 link → 不显示

3.7.4 自定义操作手册

A. 改自己的博客信息(给别人抄链用)

编辑 data/friends.json 的 my:

"my": {
  "author": "半山灯",
  "sitenick": "摸鱼处",
  "title": "浮生半刻",
  "desc": "永远相信美好的事情即将发生",
  "link": "https://blog.study996.cn/",
  "feed": "https://blog.study996.cn/atom.xml",
  "avatar": "https://file.tryrun.top/blog/logo.png",
  "archs": ["Hugo", "Static"],
  "date": "2019-07-19",
  "comment": "这是我自己"
}

保存后 npm run dev,打开 /link/ 看底部「我的博客信息」。

B. 新增一条友链(最常见)

  1. 打开 data/friends.json
  2. 找到目标分组的 entries 数组
  3. 在数组里追加一个对象(注意逗号):
{
  "author": "示例博主",
  "sitenick": "示例站",
  "title": "示例博客",
  "desc": "一句话介绍",
  "link": "https://example.com/",
  "feed": "https://example.com/index.xml",
  "avatar": "https://example.com/avatar.png",
  "archs": ["Hugo"],
  "date": "2024-01-01",
  "comment": "2026-07 交换",
  "error": ""
}

最少可写成:

{
  "author": "示例博主",
  "link": "https://example.com/",
  "avatar": "https://example.com/avatar.png"
}
  1. 保存 → 刷新 /link/

C. 新增一个分组

在 groups 数组末尾追加:

{
  "name": "推荐",
  "desc": "值得一看",
  "entries": [
    {
      "author": "另一个朋友",
      "sitenick": "Tech",
      "link": "https://friend.example/",
      "avatar": "https://friend.example/logo.png",
      "feed": ""
    }
  ]
}

分组顺序 = 页面从上到下的顺序(与 randomInGroup 无关;随机只打乱组内卡片)。

D. 隐藏失效友链(推荐,不删数据)

把该条的 error 从 "" 改成原因:

"error": "站点无法访问"

页面计数与列表都会排除它;以后恢复只要改回 "" 或删掉 error 字段。

E. 关闭组内随机

"randomInGroup": false

F. 改申请说明 / 页标题

编辑 content/link.md:

---
title: "友链"
description: "友链页面,收集添加本站为友链的网站与订阅列表。"
layout: "link"
url: /link
aside:
  - stats
  - log
---

## 申请友链

- 申请要求:……
- 申请方式:发送邮件到 `you@example.com`
  • front matter 管标题、简介、URL、右侧栏
  • 正文 Markdown 管申请规则
  • 不要把友链站点列表写进 link.md,列表只放 JSON

3.7.5 最小完整示例(可整文件替换试手)

{
  "randomInGroup": true,
  "my": {
    "author": "半山灯",
    "sitenick": "摸鱼处",
    "title": "浮生半刻",
    "desc": "永远相信美好的事情即将发生",
    "link": "https://blog.study996.cn/",
    "feed": "https://blog.study996.cn/atom.xml",
    "avatar": "https://file.tryrun.top/blog/logo.png",
    "archs": ["Hugo", "Static"],
    "date": "2019-07-19",
    "comment": "这是我自己"
  },
  "groups": [
    {
      "name": "好友",
      "desc": "长期交换友链的朋友",
      "entries": [
        {
          "author": "示例博主",
          "sitenick": "示例站",
          "title": "示例博客",
          "desc": "一句话介绍",
          "link": "https://example.com/",
          "feed": "https://example.com/index.xml",
          "avatar": "https://example.com/avatar.png",
          "error": ""
        },
        {
          "author": "已失效站点",
          "link": "https://dead.example/",
          "avatar": "",
          "error": "站点无法访问"
        }
      ]
    }
  ]
}

上面示例最终会显示 1 组 · 1 个站点(失效那条被隐藏)。

3.7.6 从旧 Nuxt 站点再导出(可选,慎用)

python scripts/export-friends.py
  • 默认读取上级目录 app/feeds.ts
  • 可用环境变量 BLOG_ROOT 指定 Nuxt 根目录
  • 整文件覆盖 data/friends.json:手改前请先备份

3.7.7 校验清单

  • JSON 语法合法(最后一项不要多余逗号、引号成对)
  • 每条有效友链都有 https://... 的 link
  • 头像用可公网访问 URL
  • 改完后看 /link:分组数、站点数、卡片头像与标题是否正确
  • 生产环境执行 npm run build 再部署

3.x 文章 description 打字机

文章页摘要默认逐字出现,速度可配:

[params.typewriter]
  enable = true
  speed = 36        # 每字毫秒,越小越快(8–200)
  startDelay = 280
  cursor = true

单页 front matter:

typewriter: false           # 关闭
typewriterSpeed: 50         # 覆盖速度
typewriterDelay: 0
typewriterCursor: false

点击摘要可跳过动画;系统「减少动态效果」时直接显示全文。

3.8 评论

说说页 /essay、相册列表 /album、相册详情默认开启(onEssay / onAlbum)。

[params.comments]
  enable = false
  provider = "twikoo"
  onPost = true
  onLink = true
  onEssay = true
  onAlbum = true
  # 相册列表 /album 与相册详情都会显示

3.9 代码注入

# [params.inject]
#   head = "<!-- 全局 -->"
#   contentHead = "<!-- 内容页 -->"
#   footer = "<!-- 页脚 -->"

3.10 自定义页脚 → data/footer.toml

页脚与原 Nuxt Clarity 一致:分组导航 + HTML 版权行。优先读 data/footer.toml,避免 hugo.toml 过长。

enable = true
copyright = "© {year} {author}"   # 支持 HTML;占位符 {year}/{author}/{title}

[license]
  enable = true
  abbr = "CC BY-NC-SA 4.0"
  name = "署名 - 非商业性使用 - 相同方式共享 4.0 国际"
  url = "https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh-hans"

[[nav]]
  title = "探索"
  [[nav.items]]
    icon = "rss"          # 完整名单与自定义见 §3.13
    text = "RSS"
    url = "/index.xml"    # 站内路径或 https / mailto
  [[nav.items]]
    icon = "subway"
    text = "开往"
    url = "https://www.travellings.cn/go.html"
能力写法
开关整页脚enable = false
版权 HTMLcopyright = "© 2026 半山灯<br> forever…"
追加 HTMLextras = ['<p>…</p>']
外链新标签默认 http(s) 自动 target=_blank;可用 newtab = false 关闭
单页隐藏front matter hideFooter = true
单页追加front matter footerHtml = "<p>…</p>"

兼容:若无 data/footer.*,仍可读旧的 params.footerNav + params.copyright。

3.11 代码块(高亮 / 复制 / 换行 / 折叠)

围栏代码会渲染为 z-codeblock(Chroma 高亮 + 工具条):

```js
console.log('hello')
```

```js {filename="app.js"}
export const n = 1
```

```bash {wrap=true}
echo "long line...."
```

```ts {expand=true}
// 超长代码默认折叠;expand=true 强制展开
```

站点配置(hugo.toml):

[params.codeblock]
  triggerRows = 32   # 超过该行数出现折叠按钮
  collapsedRows = 16 # 折叠时可见行数
  tabSize = 3

语法高亮本身由 [markup.highlight] 控制(noClasses = false,样式见 themes/clarity/assets/css/chroma.css)。

3.12 性能与字体(Lighthouse)

远程封面图(如 file.tryrun.top)的体积与 Cache-TTL 由图床/CDN 决定,主题无法单方面把 Lighthouse 图片项拉满。上线前请:上传接近展示宽度的 WebP(首页封面建议 ≤ 800px 宽)、并把图床缓存调到 ≥ 7 天。

主题已做多轮性能优化(首页 JSON 瘦身、核心/正文/归档/搜索 CSS 按需拆分、首页精选 CSS 内联避免额外阻塞请求、LCP 图 preload + 单一 fetchpriority、入口动画不隐藏首屏、首页/归档默认不整表重绘、远程字体空闲后再挂、分类色 AA 对比)。

[params.fonts]
  mode = "balanced"   # system | balanced | full
  • system:冲 Lighthouse 推荐。不上远程字体,分数通常最高
  • balanced(默认):空闲时再加载标题抖音字体;文章页按需宋体/等宽
  • full:Inter + 抖音 + 宋体 + 等宽(最接近原 Nuxt,分最低)

若线上 Performance 仍 <70,优先:

  1. params.fonts.mode = "system"
  2. 图床封面压到 WebP / 合适宽度(LCP 常见瓶颈在 file.tryrun.top 大图,不在主题 HTML)
  3. 开启 CDN 缓存与 Brotli/Gzip(static/_headers 已给指纹 CSS/JS 长期缓存)
  4. 评论、Umami、自定义 inject 脚本会拖主线程,非必要别全局注入

3.13 图标(Icon)配置教程

主题图标是 内联 SVG,不依赖 Iconify/字体图标 CDN。配置里只写 图标名字符串,模板通过 partial "icon.html" "名字" 渲染。

用在哪里

位置配置字段
左侧栏主导航hugo.toml → [[params.nav]] → [[params.nav.items]]icon / text / url
左侧栏底部图标条hugo.toml → [[params.iconNav]]icon / text / url
页脚分组链接data/footer.toml → [[nav]] → [[nav.items]]icon / text / url
主题内部 UI搜索、主题切换、精选箭头等写死在模板,一般不用改

站内路径建议无尾斜杠:url = "/essay"。http(s):// 外链会自动 target="_blank"。

快速改导航图标

编辑 hugo.toml:

# 左侧栏主导航
[[params.nav]]
  title = ""
  [[params.nav.items]]
    icon = "files"      # ← 图标名(见下方清单)
    text = "文章"
    url = "/"
  [[params.nav.items]]
    icon = "pen"
    text = "说说"
    url = "/essay"
  [[params.nav.items]]
    icon = "images"
    text = "相册"
    url = "/album"

# 左侧栏底部小图标(个人主页 / GitHub / RSS…)
[[params.iconNav]]
  icon = "home"
  text = "个人主页"                 # 鼠标悬停 title
  url = "https://www.study996.cn/"
[[params.iconNav]]
  icon = "github"
  text = "GitHub"
  url = "https://github.com/你的ID"
[[params.iconNav]]
  icon = "rss"
  text = "RSS"
  url = "/index.xml"

页脚同理,改 data/footer.toml:

[[nav]]
  title = "探索"
  [[nav.items]]
    icon = "rss"
    text = "RSS"
    url = "/index.xml"
  [[nav.items]]
    icon = "mail"
    text = "邮箱"
    url = "mailto:you@example.com"

改完后:

npm run dev
# 或
npm run build

刷新页面即可看到新图标。名字写错时会显示一个 圆形占位图标(不会崩页)。

内置图标名清单

来源:themes/clarity/layouts/partials/icon.html。

导航常用

名字适合
files文章 / 列表
pen / pencil说说 / 写作
images / image相册
link友链
archive归档
tags / tag标签
user关于
home主页
search搜索
folder分类

社交 / 外链

名字适合
githubGitHub
rssRSS / Atom
mail邮箱
message社区 / 留言
subway开往等友链列车
certificate协议 / 证书
badge-check认证 / 精选

界面杂项(一般给主题自己用,也能配到导航)

名字说明
sun / moon / desktop浅色 / 深色 / 跟随系统
menu / sidebar / align-right面板 / 侧栏
sparkles装饰
clock时间
pilcrow字数
palette主题色
map-pin地点
bell-off通知关闭
arrow-up / chevron-left / chevron-right / chevron-up方向
mouse精选滚动提示
sort-desc排序
copy / check复制成功

增删改导航项

  • 新增一项:再写一组 [[params.nav.items]](或 [[params.iconNav]] / 页脚 [[nav.items]])。
  • 删除一项:删掉对应 [[...]] 整块。
  • 分组标题:[[params.nav]] 的 title = "写作" 会在侧栏显示分组小标题;空字符串则不显示。
  • 只要图标不要字:侧栏主导航仍会显示 text;底部 iconNav 主要靠图标 + title 悬停文字。

自定义新图标(扩展主题)

  1. 打开 themes/clarity/layouts/partials/icon.html
  2. 在最后一个 {{- else -}} 之前增加分支,例如:
{{- else if eq $n "bilibili" -}}
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true">
  <!-- 粘贴 24x24 path,描边图标建议 fill="none" stroke="currentColor" -->
  <path d="M..."/>
</svg>
  1. 配置里写 icon = "bilibili"
  2. 重新 npm run dev / npm run build

建议:

  • 统一 viewBox="0 0 24 24",并带 class="icon"、aria-hidden="true"
  • 路径用 currentColor,才能跟随亮/暗主题变色
  • 可从 Lucide 等开源图标复制 SVG path(注意许可证)
  • 不要把整站改成远程 <img> 图标,以免拖慢首屏

站点图标(favicon)

这和导航 SVG 不是一回事,在 hugo.toml:

[params]
  favicon = "https://file.tryrun.top/blog/favicon.ico"
  # 或站点内静态文件:
  # favicon = "/favicon.ico"   # 对应 static/favicon.ico

校验清单

  1. icon 名字在上表或 icon.html 里存在
  2. url 站内无尾斜杠(/link 而不是 /link/)
  3. 外链用完整 https://...;邮箱用 mailto:
  4. text 有意义(无障碍 / 悬停提示)
  5. 改完构建或 dev 预览侧栏 + 页脚都看过一眼

4. 内容写作摘要

类型位置
文章content/posts/*.md,url: /archives/...
说说data/talks.json
相册content/album/*.md
友链data/friends.json + content/link.md
更新日志data/changelog.json

Shortcodes:alert tip note folding linkcard badge quote video-embed。



性能说明(已内置)

主题默认做了这些优化,上线一般无需额外配置:

优化行为
KaTeX按需加载:仅 math: true / 站点 params.math 或正文含真实公式时才加载 CSS/JS
字体加载远程字体 空闲注入;mode=system 可冲分
代码高亮按需加载 chroma.css + codeblock.js;复制 / 换行 / 超长折叠(params.codeblock)
字体Google 字体只拉常用字重(Mono 400/500/600,宋体 400/600/700),并 preconnect
图片列表懒加载 + sizes;首图 fetchpriority=high;首屏卡片不做隐藏入场动画
缓存头static/_headers:指纹 CSS/JS 长期缓存,Pagefind 一周缓存
首页/归档 JS默认视图与 SSR 一致时不整表重绘,保护 LCP
CSS 拆分精选/友链/说说相册/评论样式按页加载
关键路径 CSS首页主 CSS ~38KB(原 ~52KB)

公式文章 front matter:

math: true

5. 搜索与部署

5.1 本地构建

npm run build
# 确认 public/pagefind/pagefind.js
# 确认 public/_redirects

npm run build 会依次:清理 public/ → 内容检查 → Hugo → Pagefind → 生成无尾斜杠跳转规则。

上线必须上传整个 public/(含 pagefind/、css/、js/、_redirects、_headers)。

通用清单:

  1. baseURL / zh-cn
  2. 友链 friends.json / 日志 changelog.json 已自定义
  3. 技术信息、社区、ICP
  4. build 成功且含 Pagefind
  5. /link、/essay、/album、搜索正常
  6. 不要在 CDN/主机开启「强制尾部斜杠」
  7. params.trailingSlash = false

环境要求:

依赖版本
HugoExtended ≥ 0.158(推荐 0.164+)(推荐 0.158+)
Node.js≥ 18
包管理npm(已含 pagefind)

CI 平台若未预装 Hugo,使用:

npm run build:ci
# = ensure-hugo(按需下载 Extended)+ npm run build

可用环境变量 HUGO_VERSION 指定版本(默认 0.164.0)。


5.2 Cloudflare Pages

最省心:原生支持 _redirects + _headers(本主题构建后都会进 public/)。

  1. Cloudflare Dashboard → Workers & Pages → Create → Pages → 连接 Git 仓库(根目录选 hugo-clarity,或 monorepo 填 Root directory)。
  2. 构建设置:
项值
Framework presetNone(不要用纯 Hugo 预设,否则可能跳过 npm/Pagefind)
Build commandnpm run build 或 npm run build:ci
Build output directorypublic
Root directory主题站点目录(本仓库即 .)
  1. 环境变量(Settings → Environment variables):
变量建议值说明
NODE_VERSION22或 20 / 18
HUGO_VERSION0.164.0Cloudflare 会按此安装 Hugo;若未生效请用 build:ci
NODE_ENVproduction可选
  1. 部署后抽查:
  • https://你的域名/link(不要变成 /link/)
  • https://你的域名/archives/某文章id
  • 站内搜索(依赖 /pagefind/)
  • 打开 /_redirects 源文件应能看到 /link/ /link 301 一类规则
  1. 注意:
  • 关闭任何「Always use trailing slash / 强制尾斜杠」类选项(若控制台有)。
  • static/_headers 会复制到 public/_headers,指纹 CSS/JS 长缓存、Pagefind 一周缓存。
  • 自定义域名在 Cloudflare 里绑到该 Pages 项目即可;hugo.toml 的 baseURL 改成最终域名。

5.3 Vercel

Vercel 不读 Netlify 风格的 _redirects。本仓库根目录已提供 vercel.json:

  • trailingSlash: false → /link/ 自动规范到 /link
  • outputDirectory: public
  • 静态资源 Cache-Control 与安全头

Git 导入

  1. Vercel → Add New Project → 导入含本站的仓库。
  2. 若 monorepo:Root Directory 选 hugo-clarity。
  3. 框架预设选 Other。
  4. 确认(一般已由 vercel.json 写好):
项值
Build Commandnpm run build:ci
Output Directorypublic
Install Commandnpm install

本地已装 Hugo 时也可用 npm run build;Vercel Linux 构建机通常没有 Hugo,推荐 build:ci。

  1. 环境变量(可选):
变量值
HUGO_VERSION0.164.0
NODE_VERSION 或 Project Node.js Version22.x
  1. 部署后验证无尾斜杠与搜索;vercel.json 已禁止 trailing slash,无需再手写海量 redirect。

CLI 部署

npm i -g vercel
npm run build:ci   # 或本地已有 hugo 时 npm run build
vercel --prod

5.4 腾讯云 EdgeOne Pages

适合国内线路与现有腾讯云域名。仓库根目录提供 edgeone.json(缓存与安全响应头)。尾斜杠跳转优先用构建产物 public/_redirects。

方式 A:控制台 Git 持续部署

  1. 打开 EdgeOne Pages → 创建项目 → 绑定 Git 仓库。
  2. 构建设置:
项值
构建命令npm run build:ci
输出目录public
Node 版本18+(推荐 20/22)
项目根目录hugo-clarity(若在 monorepo 中)
  1. 环境变量:HUGO_VERSION=0.164.0(可选,build:ci 会下载 Extended)。
  2. 部署完成后在 EdgeOne 绑定加速域名 / 自定义域名,HTTPS 按控制台指引开启。
  3. 在「重定向 / 路由」类设置中:不要开启强制尾部斜杠。
  4. 构建产物含 public/_redirects(/path/ → 301 /path)。若 EdgeOne 支持该文件会自动生效;否则请在控制台添加同类规则,或依赖主题前端去尾斜杠兜底。
  5. 根目录 edgeone.json 主要提供缓存与安全响应头,可按需改 headers。

方式 B:本地构建后上传

npm run build
# 将整个 public/ 作为静态资源上传到 EdgeOne Pages「直接上传」或对象存储 + 边缘加速

上传后同样检查 /link、/archives/id、搜索与 pagefind/ 是否可访问。

edgeone.json 说明

  • headers:CSS/JS 长缓存、Pagefind 缓存、基础安全头(可手改,勿删关键项)。
  • redirects:默认留空。优先依赖构建产物 public/_redirects(平台若支持),或在 EdgeOne 控制台关闭强制尾斜杠;主题内也有 history.replaceState 兜底。

5.5 自建 Nginx(补充)

仓库已提供示例:deploy/nginx-no-trailing-slash.conf

要点:

rewrite ^/(.*)/$ /\$1 permanent;
location / {
  try_files $uri $uri.html $uri/index.html =404;
}

root 指向 public/。


5.6 平台对比速查

平台构建命令输出目录无尾斜杠Pagefind配置文件
Cloudflare Pagesnpm run build / build:cipublic_redirects + 关强制尾斜杠随 public/public/_headers
Vercelnpm run build:cipublicvercel.json → trailingSlash:false随 public/vercel.json
EdgeOne Pagesnpm run build:cipublic_redirects + edgeone.json随 public/edgeone.json
Nginx 自建本地 npm run buildpublicdeploy/nginx-*.conf随 public/nginx conf

共同坑:

  1. 只上传了 HTML、漏了 pagefind/ → 搜索空白。
  2. baseURL 仍是 http://127.0.0.1 或错域名 → 规范链接/OG 错误。
  3. 平台「Pretty URL / 强制 /」打开 → 又变回 /link/。
  4. 用纯 Hugo 预设构建、没跑 npm → 没有 Pagefind、没有 _redirects。
  5. Vercel 用默认 Build 却系统无 hugo → 构建失败,改用 build:ci。

6. 常见问题

Q: 为什么是 /link、/archives/id,而不是最后带 /?
A: 默认 params.trailingSlash = false(见 §3.1.1)。需要尾斜杠时设为 true,并把菜单改成 /link/ 等形式。

Q: npm run dev 与 hugo server 有何区别?
A: npm run dev = Hugo 写盘 + clean-server,地址为 /link。裸 hugo server 会强制 /link/。

Q: 本地访问 /link 被跳到 /link/?
A: 部分服务器会把目录索引规范化成尾斜杠。站内生成的链接已是无尾斜杠;部署时在 CDN/主机关闭「强制尾部斜杠」即可与线上一致。

Q: 友链怎么自定义 / 加一个站?
A: 编辑 data/friends.json。在某个 groups[].entries 追加对象,至少写 link,建议再写 author、avatar。详细步骤见 §3.7.4。

Q: 友链不显示?
A: 依次查:JSON 是否合法 → 是否写在 groups[].entries → link 是否为空 → error 是否非空字符串。

Q: 页面一进就乱序 / 点标题会乱序?
A: randomInGroup: true 会在加载时随机一次,点击分组标题再随机;设为 false 可关闭。Ctrl/Cmd/Shift/Alt + 点击可恢复原序。

Q: 自己的博客信息怎么改?
A: 改 friends.json 的 my 字段;不要写在 hugo.toml。

Q: export-friends 把我手改冲掉了?
A: 导出是整表覆盖;手改前请备份 friends.json。

Q: 更新日志在哪改?
A: data/changelog.json,不在 hugo.toml。

Q: 页脚怎么改 / 备案号写哪?
A: 编辑 data/footer.toml(见 §3.10)。导航分组在 [[nav]],备案可放在「信息」分组的 [[nav.items]],版权用 copyright(支持 HTML 与 {year})。

Q: 搜索无结果?
A: 先 npm run build 并部署 pagefind/。

Q: Vercel / Cloudflare / EdgeOne 怎么部署?
A: 见 §5.2–5.4。CI 无 Hugo 时用 npm run build:ci;输出目录一律 public;关闭强制尾斜杠。

Q: Vercel 构建报 hugo: not found?
A: Build Command 改为 npm run build:ci(会下载 Hugo Extended),或在环境里自备 hugo。


7. 上线检查清单

必做

  1. baseURL 已是线上域名(现 https://blog.study996.cn/)
  2. defaultContentLanguage = "zh-cn" + locale = "zh-cn"(全小写)
  3. trailingSlash = false,本地用 npm run dev 验证无尾斜杠
  4. npm run build 成功
  5. public/pagefind/pagefind.js 存在
  6. public/_redirects 存在
  7. 抽查 / /link /essay /about /archive /tags /archives/某id /album
  8. 上传整个 public/(含 pagefind/、_redirects、css/)

按需

  • 评论:params.comments.enable = true 并填 provider 凭证
  • Umami:取消注释 params.umami
  • 自定义 data/friends.json / talks.json / changelog.json
  • 主机关闭「强制尾部斜杠」,或使用 deploy/nginx-no-trailing-slash.conf
  • 若用 Vercel / Cloudflare / EdgeOne:按 §5.2–5.4 配置,构建命令优先 npm run build:ci
  • 确认 vercel.json / edgeone.json / public/_redirects 与目标平台匹配

不要

  • 不要用裸 hugo server 判断尾斜杠(会强制 /page/)
  • 不要只上传 html 而漏掉 pagefind/

8. 致谢

视觉来源 L33Z22L11/blog-v3。主题 MIT。