Deploying Hexo with GitHub Actions

Notes on setting up automated Hexo deployment via GitHub Actions, including the branch structure and the pitfalls encountered.

Branch structure

The repository uses three branches with distinct roles:

Branch Purpose
main Portfolio site — served by Nginx on the VPS
hexo-source Hexo blog source (Markdown, config, themes)
gh-pages Compiled static output — legacy, no longer used

GitHub Pages source is set to GitHub Actions, not a branch. The Action builds from hexo-source and deploys the compiled public/ directory directly.

How GitHub Actions works

An Action is a YAML workflow file stored in .github/workflows/. GitHub runs it on its own servers when a trigger fires — no local environment involved.

1
2
3
4
5
6
7
8
9
push to hexo-source

GitHub runs .github/workflows/pages.yml

npm install + hexo generate → public/

actions/deploy-pages → GitHub Pages CDN

blog.xiofelix.com

The workflow file

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
name: Pages

on:
push:
branches:
- hexo-source

jobs:
build:
runs-on: ubuntu-latest
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
submodules: recursive
- uses: actions/setup-node@v4
with:
node-version: '22'
- uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-npm-cache-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-cache-
- run: npm install
- run: npm run build
- uses: actions/upload-pages-artifact@v3
with:
path: ./public

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

Key points:

  • on.push.branches: [hexo-source] — only fires on pushes to the source branch, not main
  • actions/cache keyed on package-lock.json hash — rebuilds node_modules only when dependencies change
  • FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 — suppresses the Node.js 20 deprecation warning
  • Two separate jobs: build produces the artifact, deploy publishes it — needs: build enforces order

Pitfalls

Wrong trigger branch. The original workflow listened to main. Since Hexo source moved to hexo-source, every push was silently ignored. Fix: update on.push.branches.

Environment protection rules. GitHub Pages deployments go through a protected environment called github-pages. By default only the default branch (main) is allowed. Deploying from hexo-source fails with:

1
2
Branch "hexo-source" is not allowed to deploy to github-pages
due to environment protection rules.

Fix: Settings → Environments → github-pages → Deployment branches → Add rule → hexo-source

Branch source vs Actions source. If Pages source is set to “Deploy from a branch: hexo-source”, GitHub serves the raw Markdown and config files — not the compiled HTML. The correct setting is “GitHub Actions”.

Publishing workflow

1
2
3
4
5
6
7
8
cd ~/github-pages-work

# write or edit a post
hexo new "post title"

git add .
git commit -m "feat: new post"
git push origin hexo-source # triggers the Action automatically

The Action takes about 60 seconds. The blog updates without any further manual steps.

记录通过 GitHub Actions 自动化部署 Hexo 博客的完整流程,包括分支结构设计和实际踩过的坑。

分支结构

仓库使用三个职责明确的分支:

分支 用途
main 作品集网站 — 由 VPS 上的 Nginx 直接服务
hexo-source Hexo 博客源码(Markdown、配置、主题)
gh-pages 编译后的静态文件 — 历史遗留,已不再使用

GitHub Pages 的来源设置为 GitHub Actions,而不是某个分支。Actions 从 hexo-source 构建,并直接将编译后的 public/ 目录部署上线。

GitHub Actions 的工作原理

Action 是存储在 .github/workflows/ 目录下的 YAML 工作流文件。每当触发器触发时,GitHub 会在自己的服务器上自动运行它,不依赖本地环境。

1
2
3
4
5
6
7
8
9
push 到 hexo-source

GitHub 运行 .github/workflows/pages.yml

npm install + hexo generate → public/

actions/deploy-pages → GitHub Pages CDN

blog.xiofelix.com

Workflow 文件

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
name: Pages

on:
push:
branches:
- hexo-source

jobs:
build:
runs-on: ubuntu-latest
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
submodules: recursive
- uses: actions/setup-node@v4
with:
node-version: '22'
- uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-npm-cache-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-cache-
- run: npm install
- run: npm run build
- uses: actions/upload-pages-artifact@v3
with:
path: ./public

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

关键点:

  • on.push.branches: [hexo-source] — 只在 hexo-source 分支有 push 时触发,不影响 main
  • actions/cachepackage-lock.json 哈希为 key — 依赖不变时直接复用缓存
  • FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 — 消除 Node.js 20 弃用警告
  • 两个独立 job:build 生成构建产物,deploy 负责发布,needs: build 保证顺序

踩过的坑

触发分支配置错误。 原始 workflow 监听的是 main,但 Hexo 源码在 hexo-source,导致每次 push 都被静默忽略。解决:更新 on.push.branches

环境保护规则。 GitHub Pages 部署通过名为 github-pages 的受保护环境进行。默认只允许默认分支(main)部署,从 hexo-source 部署会报错:

1
2
Branch "hexo-source" is not allowed to deploy to github-pages
due to environment protection rules.

解决:Settings → Environments → github-pages → Deployment branches → Add rule → hexo-source

branch source 与 Actions source 的区别。 如果 Pages 来源设置为”从 hexo-source 分支部署”,GitHub 会直接托管原始 Markdown 和配置文件,而不是编译后的 HTML。正确设置应为 “GitHub Actions”。

日常发布流程

1
2
3
4
5
6
7
8
cd ~/github-pages-work

# 写文章或修改配置
hexo new "文章标题"

git add .
git commit -m "feat: 新文章"
git push origin hexo-source # 自动触发 Action,博客自动更新

Action 大约需要 60 秒完成。推送之后无需任何手动操作,博客自动更新。


Deploying Hexo with GitHub Actions
https://blog.xiofelix.com/2026/04/13/hexo-github-actions-deployment/
Author
Felix
Posted on
April 13, 2026
Licensed under