每次写完一篇博客,在本地 hexo s 预览完觉得没问题了,还得手动敲一遍 hexo g && hexo d,等它慢慢推到 GitHub 上……说实话,烦死了。有几次我写完文章都凌晨两点了,敲完部署命令就去睡了,第二天起来发现 TM 的部署失败了,原因是本地 Node 版本跟远程不一致,生成的页面样式全崩了。

后来我寻思,这玩意儿不就是 CI/CD 最典型的场景吗?push 代码 → 自动构建 → 自动部署。GitHub Actions 免费额度够用,配一次永久生效,咱就搞起来吧。

为什么要自动化部署

先说说我之前手动部署的痛苦:

  1. 本地环境不稳定 —— 我电脑上装了 Node 16、18、20 三个版本(用 nvm 管理的),有时候切错版本,hexo generate 直接报错
  2. 忘记部署 —— 有好几次文章写完了,commit 推上去了,结果忘了 hexo d,过了两天才发现博客上还是旧内容
  3. 换电脑就抓瞎 —— 出去玩的时候用笔记本写文章,结果那台机器没装 Hexo 环境,还得现装

用 GitHub Actions 的话,这些问题统统不存在了。只要 git push,剩下的交给 CI 搞定。

GitHub Pages 的两种部署方式

现在 GitHub Pages 支持两种部署方式,咱先对比一下:

方式一:分支部署(传统方式)

把构建产物推到 gh-pages 分支,GitHub 从这个分支部署。用 peaceiris/actions-gh-pages 这个 Action 可以轻松搞定,也是目前 Hexo 社区用得最多的方案。

优点:配置简单,社区成熟,坑少
缺点:需要维护一个 gh-pages 分支

方式二:GitHub Actions 部署(官方新方式)

2022 年 GitHub 推出的官方方案,使用 actions/upload-pages-artifact + actions/deploy-pages,直接通过 Artifact 部署,不需要 gh-pages 分支。

优点:官方方案,不需要额外分支
缺点:需要配置 OIDC 权限(id-token: write),稍微复杂一点

两种方式都能用,选哪个看个人喜好。我个人推荐方式一,因为 peaceiris/actions-gh-pages 配置更简洁,踩坑也少。

完整 Workflow 配置

方式一:使用 peaceiris/actions-gh-pages(推荐)

在你的 Hexo 博客根目录下创建 .github/workflows/deploy.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
name: Deploy Hexo Blog

on:
push:
branches:
- master # 推送到 master 分支时触发

jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: write # 需要写权限,否则 push 到 gh-pages 会 403

steps:
- name: Checkout source
uses: actions/checkout@v4
with:
submodules: true # 重要!Hexo 主题通常是 submodule
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # 缓存 npm 依赖,加速构建

- name: Install dependencies
run: npm ci

- name: Generate static files
run: npx hexo generate

- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public
cname: blog.jerryfage.top # 自定义域名,没有可删掉这行
配置说明
  • branches: master —— 你的 Hexo 源码分支,我用的是 master,如果你用 main 记得改
  • submodules: true —— 这行非常重要,Butterfly 主题是以 submodule 方式引入的,不加这步主题文件不会被拉下来
  • node-version: '20' —— 用 Node 20,稳定且是 LTS 版本
  • cache: 'npm' —— GitHub 自带的 npm 缓存,比手动缓存 node_modules 方便
  • npm ci —— 比 npm install 更快更严格,适合 CI 环境
  • cname —— 自定义域名配置,会自动生成 CNAME 文件。没有自定义域名就删掉这行

方式二:使用官方 actions/deploy-pages

如果你更喜欢官方方案,配置如下:

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
name: Deploy Hexo Blog

on:
push:
branches:
- master

permissions:
contents: read
pages: write
id-token: write # OIDC 认证必需

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Generate static files
run: npx hexo generate

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./public

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

使用方式二的话,需要去仓库 Settings → Pages → Source 里把 Source 改成 GitHub Actions,而不是选分支。否则部署会报错。

自定义域名 CNAME 处理

我的博客绑定了自定义域名 blog.jerryfage.top,这里有几个要注意的点:

  1. 如果用 peaceiris/actions-gh-pages:在 with 里加 cname: blog.jerryfage.top 就行,它会自动在 public/ 目录生成 CNAME 文件

  2. 如果用官方 deploy-pages:需要在 source/ 目录下手动创建 CNAME 文件,内容就一行:blog.jerryfage.top。因为 hexo generate 会把 source/ 下的所有文件复制到 public/

  3. DNS 配置:在你的域名服务商那里,添加 CNAME 记录指向 jerry-fage.github.io

1
2
# 在 Hexo 源码的 source 目录下创建 CNAME 文件
echo "blog.jerryfage.top" > source/CNAME

踩坑记录

坑一:GitHub Token 权限不足(403 错误)

peaceiris/actions-gh-pages 的时候,第一次部署大概率会遇到 403 错误。原因是 GITHUB_TOKEN 默认没有 contents: write 权限。

解决办法:在 workflow 的 jobs 下面加 permissions: contents: write,就像我上面的配置一样。

另外,第一次部署完成后,需要去仓库 Settings → Pages → Source 里手动选择 gh-pages 分支,不然 GitHub 不知道从哪里部署。

坑二:.gitignore 把 public/ 推上去了

有些同学的 .gitignore 里没有忽略 public/ 目录,结果把本地生成的文件也推到了远程。CI 环境会重新生成,这些本地产物反而会造成冲突。

检查一下你的 .gitignore,确保有这几行:

1
2
3
public/
.deploy*/
db.json
public 目录千万别推上去,CI 会自己生成,推上去只会搞乱。

坑三:Butterfly 主题 Submodule 问题

这是最容易踩的坑。如果你的主题(比如 Butterfly)是用 git submodule 方式安装的,workflow 里必须加 submodules: true,否则 checkout 下来的代码里主题目录是空的,hexo generate 会直接报错。

有些同学用的是 npm install hexo-theme-butterfly 方式安装主题,这种不需要 submodules: true,因为你 npm ci 的时候主题就装好了。但如果你是 git submodule add 方式的,这行绝对不能少。

坑四:Node 版本不一致

本地用 Node 20,CI 里用 Node 16,生成结果可能不一样。建议 workflow 里固定一个 LTS 版本,目前推荐 Node 20。

完整流程总结

整个自动化部署流程就是这样的:

  1. 本地写文章,hexo new "文章标题"
  2. 写完 git add . && git commit -m "新文章" && git push
  3. GitHub Actions 自动触发,拉代码、装依赖、生成静态页面、部署到 GitHub Pages
  4. 等两分钟,刷新博客就能看到新文章了

就这么简单,从此再也不用 hexo g && hexo d 了。

参考资料