如果你想用 Hugo 搭一个静态博客,同时又希望把站点托管到 GitHub Pages,那现在最省事的做法就是:直接在当前仓库里构建并发布。
这篇文章只讲一套当前还适合直接照着做的流程:在博客仓库里写文章,在博客仓库里跑 GitHub Actions,再由 GitHub Pages 直接发布构建结果。
一、准备 Hugo 项目
先确保你已经有一个能正常运行的 Hugo 项目。
如果你还没有项目,可以先初始化:
然后根据自己的习惯安装主题、补充配置、添加文章。
二、设置站点地址
打开 Hugo 配置文件,把 baseURL 改成你的正式访问地址。
例如:
1
| baseURL: "https://owovo.xyz"
|
如果你暂时还没有自己的域名,也可以先写成后续准备使用的 GitHub Pages 地址,等域名配置完成后再改。
三、准备构建命令
如果你的站点只需要 Hugo 本身来生成页面,那么最简单的构建脚本可以这样写:
1
2
3
4
5
| {
"scripts": {
"build:site": "hugo --gc --minify"
}
}
|
如果你的项目除了生成静态页面之外,还需要额外生成搜索索引、前端资源或别的发布产物,也可以拆开写清楚。
例如:
1
2
3
4
5
6
7
| {
"scripts": {
"build:site": "hugo --gc --minify",
"build:search": "pagefind --site public",
"build": "npm run build:site && npm run build:search"
}
}
|
提示: 如果你使用了 Dart Sass 等需要特殊运行环境的工具,Hugo 在构建时可能找不到正确的 sass 二进制文件。这种情况下可以写一个简单的 Node.js 封装脚本,在调用 hugo 前把本地工具目录注入 PATH,然后在 package.json 中通过封装脚本来执行构建:
1
2
3
4
5
| {
"scripts": {
"build:site": "node ./scripts/run-hugo.mjs --gc --minify"
}
}
|
这样无论是本地开发还是 CI 环境,Hugo 调用的都是同一套工具链,不会出现环境不一致的问题。
核心思路很简单:把你本地发布时真正要跑的命令,整理成仓库里的脚本,后面工作流直接调用它。
四、编写 GitHub Pages 工作流
在仓库里新建文件:
1
| .github/workflows/gh-pages.yml
|
可以直接使用下面这份工作流:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
| name: GitHub Pages
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
concurrency:
group: github-pages
cancel-in-progress: true
jobs:
build:
permissions:
contents: read
pages: write
actions: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Read Hugo version
id: hugo-version
run: echo "version=$(cat .hugo-version)" >> $GITHUB_OUTPUT
- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: ${{ steps.hugo-version.outputs.version }}
extended: true
- name: Setup Hugo resource cache
uses: actions/cache@v5
with:
path: ./resources/_gen
key: ${{ runner.os }}-hugo-${{ steps.hugo-version.outputs.version }}-${{ hashFiles('package-lock.json', 'config/_default/**/*.yaml') }}
restore-keys: |
${{ runner.os }}-hugo-${{ steps.hugo-version.outputs.version }}-
${{ runner.os }}-hugo-
- name: Read Node.js version
id: node-version
run: echo "version=$(cat .node-version)" >> $GITHUB_OUTPUT
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ steps.node-version.outputs.version }}
cache: "npm"
cache-dependency-path: package-lock.json
- name: Install root dependencies
run: npm ci
- name: Check format
run: npm run format:check
- name: Build site
run: |
npm run build:site > hugo-build.log 2>&1 || {
echo "::error::Hugo 构建失败"
cat hugo-build.log
exit 1
}
if grep -qi "deprecated\|WARN" hugo-build.log; then
echo "::warning::Hugo 构建输出包含弃用警告"
grep -i "deprecated\|WARN" hugo-build.log
fi
- name: Build search
run: npm run build:search
- name: Verify Pagefind artifacts
run: test -f public/pagefind/pagefind.js && test -f public/pagefind/pagefind-entry.json
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v5
with:
path: ./public
deploy:
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
|
这份工作流在做什么
上面这份配置包含了几个值得留意的地方:
1. 权限最小化
工作流顶层只声明了最基础的 contents: read,然后在每个 job 里按需声明额外权限:
1
2
3
4
5
6
7
8
9
10
11
12
13
| permissions:
contents: read # 顶层基线
jobs:
build:
permissions:
contents: read # 检出代码
pages: write # 上传 Pages 构建产物
actions: write # 保存 Hugo/npm 缓存
deploy:
permissions:
pages: write # 发布到 Pages
id-token: write # OIDC 身份认证
|
这样比把所有权限写在顶层更加精细:build 不拿 OIDC、deploy 不拿仓库内容,每个 job 只能做它该做的事。
2. Hugo 版本集中管理
把 Hugo 版本号写在一个单独的文件 .hugo-version 里:
工作流通过 Read Hugo version 步骤读取这个文件,再传给 peaceiris/actions-hugo。这样以后升级 Hugo 只需要改这一个文件,不用去翻工作流配置。
如果你使用 renovate 之类的自动更新工具,它也可以直接识别 .hugo-version 并自动提 PR。
3. Hugo 构建缓存
1
2
3
4
5
6
7
8
| - name: Setup Hugo resource cache
uses: actions/cache@v5
with:
path: ./resources/_gen
key: ${{ runner.os }}-hugo-${{ steps.hugo-version.outputs.version }}-${{ hashFiles('package-lock.json', 'config/_default/**/*.yaml') }}
restore-keys: |
${{ runner.os }}-hugo-${{ steps.hugo-version.outputs.version }}-
${{ runner.os }}-hugo-
|
Hugo 在处理 SCSS 等前端资源时,会在 resources/_gen 目录下生成缓存文件。加上缓存步骤后,后续构建可以直接复用这些中间产物,避免重复编译。
缓存的 key 里包含了 Hugo 版本、package-lock.json 和配置文件目录的哈希值——当这些依赖发生变化时,旧缓存会自动失效,不会引入不一致的问题。
4. 使用 npm 脚本作为统一入口
构建步骤中调用的 npm run build:site 和 npm run build:search 都是在 package.json 里事先定义好的脚本。这样做的好处是:本地开发和 CI 跑的是完全相同的命令,不会出现"本地能过、CI 报错"的情况。
按需裁剪
如果你的项目不需要 Node.js、也不需要额外生成搜索索引,可以把下面这些步骤删掉:
Setup Hugo resource cacheSetup Node.jsInstall dependenciesBuild search
然后把工作流里的构建命令改成只执行 Hugo 构建即可。Hugo 版本仍然建议通过 .hugo-version 管理,而不是直接写在 env 里。
五、开启 Pages 发布
工作流写好之后,进入仓库设置:
在发布来源里选择:
这样这个仓库就会使用你刚刚写好的工作流来发布站点。
六、推送代码并自动发布
把博客代码推送到 main 分支后,GitHub Actions 就会自动开始执行。
整个过程一般分成两步:
你可以在仓库的 Actions 页面查看每一次发布记录。
七、绑定自定义域名
如果你希望博客通过自己的域名访问,比如 owovo.xyz,还需要再做两件事。
1. 在 Pages 设置里填写域名
进入:
把你的正式域名填到 Custom domain 里并保存。
保存后,等 GitHub 证书准备完成,再打开 Enforce HTTPS。
2. 在域名服务商后台添加解析
这一部分要看你使用的是哪一家域名服务商,但原则很简单:
- 根域名解析到
GitHub Pages www 子域名通常可以添加一条 CNAME 指向 用户名.github.io
如果你已经换成自己的正式域名,别忘了把 Hugo 配置里的 baseURL 也同步改成这个域名。
八、适合长期维护的做法
如果你准备长期维护这个博客,建议把下面几件事固定下来:
- 内容、主题、配置和工作流都放在同一个仓库里维护。
- 把 Hugo 版本号放在
.hugo-version 文件里单独管理,工作流从文件读取而不是硬编码。升级 Hugo 时只需要改这一个地方。 - 本地构建命令和工作流构建命令保持一致,都通过
package.json 里的 npm 脚本调用。 - 为 Hugo 配置构建缓存(
actions/cache),能让每次 CI 构建快不少;缓存 key 里记得带上 Hugo 版本号,升级时旧缓存会自动失效。 - 每次改域名、改资源路径、改搜索方案时,同时检查
baseURL 和发布流程。 - 站点能正常发布后,再去处理自定义域名和 HTTPS。
按这套方式整理后,博客的日常维护会简单很多,发布链路也更直观。