浮生半刻
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
主题会处理:
- 站内链接(导航、文章卡、分页、标签…)
canonical/og:urlsitemap.xml/ RSS- 搜索索引与评论 path
- 构建后生成
public/_redirects:/path/→ 301/path(Cloudflare Pages / Netlify) - 页面内联脚本:若地址栏仍带
/,会history.replaceState去掉
内容 front matter
文章:
url: /archives/你的id
单页(示例):
# content/link.md / essay.md / about.md
url: /link # 不要写成 /link/
导航菜单(hugo.toml)同样写无尾斜杠:url = "/essay"。
上线部署
npm run build(本地已有 Hugo)或npm run build:ci(Vercel / EdgeOne 等 CI)- 上传/发布整个
public/(含pagefind/、_redirects、_headers) - 主机若强制加尾斜杠,请关闭;自建可用
deploy/nginx-no-trailing-slash.conf - 平台细则见 §5.2 Cloudflare Pages、§5.3 Vercel、§5.4 EdgeOne
- 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 从上到下是:
- 标题区:来自
content/link.md的title/description,以及 JSON 统计出的「X 组 · Y 个站点」 - 分组卡片区:来自
friends.json的groups - 我的博客信息:来自
friends.json的my(没有my就不显示) - 申请说明:来自
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. 新增一条友链(最常见)
- 打开
data/friends.json - 找到目标分组的
entries数组 - 在数组里追加一个对象(注意逗号):
{
"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"
}
- 保存 → 刷新
/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 |
| 版权 HTML | copyright = "© 2026 半山灯<br> forever…" |
| 追加 HTML | extras = ['<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,优先:
params.fonts.mode = "system"- 图床封面压到 WebP / 合适宽度(LCP 常见瓶颈在
file.tryrun.top大图,不在主题 HTML) - 开启 CDN 缓存与 Brotli/Gzip(
static/_headers已给指纹 CSS/JS 长期缓存) - 评论、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 | 分类 |
社交 / 外链
| 名字 | 适合 |
|---|---|
github | GitHub |
rss | RSS / 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悬停文字。
自定义新图标(扩展主题)
- 打开
themes/clarity/layouts/partials/icon.html - 在最后一个
{{- 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>
- 配置里写
icon = "bilibili" - 重新
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
校验清单
-
icon名字在上表或icon.html里存在 -
url站内无尾斜杠(/link而不是/link/) - 外链用完整
https://...;邮箱用mailto: -
text有意义(无障碍 / 悬停提示) - 改完构建或 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)。
通用清单:
-
baseURL/zh-cn - 友链
friends.json/ 日志changelog.json已自定义 - 技术信息、社区、ICP
- build 成功且含 Pagefind
-
/link、/essay、/album、搜索正常 - 不要在 CDN/主机开启「强制尾部斜杠」
-
params.trailingSlash = false
环境要求:
| 依赖 | 版本 |
|---|---|
| Hugo | Extended ≥ 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/)。
- Cloudflare Dashboard → Workers & Pages → Create → Pages → 连接 Git 仓库(根目录选
hugo-clarity,或 monorepo 填 Root directory)。 - 构建设置:
| 项 | 值 |
|---|---|
| Framework preset | None(不要用纯 Hugo 预设,否则可能跳过 npm/Pagefind) |
| Build command | npm run build 或 npm run build:ci |
| Build output directory | public |
| Root directory | 主题站点目录(本仓库即 .) |
- 环境变量(Settings → Environment variables):
| 变量 | 建议值 | 说明 |
|---|---|---|
NODE_VERSION | 22 | 或 20 / 18 |
HUGO_VERSION | 0.164.0 | Cloudflare 会按此安装 Hugo;若未生效请用 build:ci |
NODE_ENV | production | 可选 |
- 部署后抽查:
https://你的域名/link(不要变成/link/)https://你的域名/archives/某文章id- 站内搜索(依赖
/pagefind/) - 打开
/_redirects源文件应能看到/link/ /link 301一类规则
- 注意:
- 关闭任何「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/自动规范到/linkoutputDirectory: public- 静态资源 Cache-Control 与安全头
Git 导入
- Vercel → Add New Project → 导入含本站的仓库。
- 若 monorepo:Root Directory 选
hugo-clarity。 - 框架预设选 Other。
- 确认(一般已由
vercel.json写好):
| 项 | 值 |
|---|---|
| Build Command | npm run build:ci |
| Output Directory | public |
| Install Command | npm install |
本地已装 Hugo 时也可用
npm run build;Vercel Linux 构建机通常没有 Hugo,推荐build:ci。
- 环境变量(可选):
| 变量 | 值 |
|---|---|
HUGO_VERSION | 0.164.0 |
NODE_VERSION 或 Project Node.js Version | 22.x |
- 部署后验证无尾斜杠与搜索;
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 持续部署
- 打开 EdgeOne Pages → 创建项目 → 绑定 Git 仓库。
- 构建设置:
| 项 | 值 |
|---|---|
| 构建命令 | npm run build:ci |
| 输出目录 | public |
| Node 版本 | 18+(推荐 20/22) |
| 项目根目录 | hugo-clarity(若在 monorepo 中) |
- 环境变量:
HUGO_VERSION=0.164.0(可选,build:ci会下载 Extended)。 - 部署完成后在 EdgeOne 绑定加速域名 / 自定义域名,HTTPS 按控制台指引开启。
- 在「重定向 / 路由」类设置中:不要开启强制尾部斜杠。
- 构建产物含
public/_redirects(/path/→ 301/path)。若 EdgeOne 支持该文件会自动生效;否则请在控制台添加同类规则,或依赖主题前端去尾斜杠兜底。 - 根目录
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 Pages | npm run build / build:ci | public | _redirects + 关强制尾斜杠 | 随 public/ | public/_headers |
| Vercel | npm run build:ci | public | vercel.json → trailingSlash:false | 随 public/ | vercel.json |
| EdgeOne Pages | npm run build:ci | public | _redirects + edgeone.json | 随 public/ | edgeone.json |
| Nginx 自建 | 本地 npm run build | public | deploy/nginx-*.conf | 随 public/ | nginx conf |
共同坑:
- 只上传了 HTML、漏了
pagefind/→ 搜索空白。 baseURL仍是http://127.0.0.1或错域名 → 规范链接/OG 错误。- 平台「Pretty URL / 强制 /」打开 → 又变回
/link/。 - 用纯 Hugo 预设构建、没跑 npm → 没有 Pagefind、没有
_redirects。 - 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. 上线检查清单
必做
-
baseURL已是线上域名(现https://blog.study996.cn/) -
defaultContentLanguage = "zh-cn"+locale = "zh-cn"(全小写) -
trailingSlash = false,本地用npm run dev验证无尾斜杠 -
npm run build成功 -
public/pagefind/pagefind.js存在 -
public/_redirects存在 - 抽查
//link/essay/about/archive/tags/archives/某id/album - 上传整个
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。