Hexo + GitHub Pages + GitHub Actions:源码私有、站点公开的 CI/CD 教程

最近正好想要使用HEXO在部署一次自己的博客,但每次都要受到提交运行太麻烦了,好在GitHub Actions可以帮助我们自动化这个过程,而且目前还是免费的。

最终目标

  • Hexo 源码仓库可以保持私有(文章源文件、主题配置、脚本都在这里维护)
  • 对外访问的站点仓库保持公开(只存放构建后的静态文件,给 GitHub Pages 发布)
  • 只要我 push 源码,GitHub Actions 就自动构建并把 public/ 推送到 Pages 仓库

下面按这个思路把整套流程梳理一遍(也适用于"源码公开/私有"两种情况)。

方案概览

仓库职责

  • 源码仓库(例如:hexo-blog,可私有)
    • 存放:Hexo 项目源码(source/_posts、主题、_config.ymlpackage.json 等)
    • 负责:GitHub Actions 构建与部署
  • Pages 仓库(用户站点):<你的用户名>.github.io(必须公开)
    • 存放:Hexo 生成的静态文件(public/ 目录里的 HTML/CSS/JS/图片等)
    • 负责:开启 GitHub Pages 对外访问

自动化流程

  1. 你在源码仓库写文章并 push
  2. GitHub Actions 触发:安装依赖 → hexo generate 生成静态站点到 public/
  3. Actions 把 public/ 推送到 Pages 仓库指定分支(通常 main
  4. GitHub Pages 直接从 Pages 仓库发布

费用与计费简单说明

具体计费情况可以查看 GitHub Actions 计费页面 了解详情。

  • 公共仓库的 Actions 通常免费
  • 私有仓库使用 GitHub 托管运行器有免费额度,超出可能计费(分钟数/存储与账号计划相关)
  • 缓存/工件存储也可能产生占用,删除后不会继续累积

对个人博客来说,构建频率不高、站点不大时,通常不会超过免费额度。想更省:减少触发频率、开启依赖缓存、不要上传大工件。

前置准备

  • GitHub 账号
  • 本地安装 Node.js(建议 LTS)与 Git
  • 一个可正常本地运行的 Hexo 项目(本文默认依赖已在 package.json 中)

创建并配置两个仓库

1) 创建 Pages 仓库(公开)

  1. 新建仓库:仓库名必须是 <你的用户名>.github.io
  2. 仓库可见性:Public
  3. Settings → Pages
    • Source:Deploy from a branch
    • Branch:main / (root)(推荐)

2) 创建/准备源码仓库(可私有)

把 Hexo 源码提交到源码仓库即可,文章建议放在:

  • source/_posts/(Hexo 默认文章目录)

配置 GitHub Actions:构建后推送到另一个仓库

关键点是:Actions 在源码仓库里构建完,需要"跨仓库写入" Pages 仓库。

常见做法:

  • Fine-grained PAT(更直观)
  • Deploy Key(SSH,适合更严格的权限隔离)

这里使用 Fine-grained PAT。

1) 创建 Fine-grained PAT

GitHub:Settings → Developer settings → Personal access tokens → Fine-grained tokens:

  1. 选择只授权给你的 Pages 仓库(<你的用户名>.github.io
  2. Permissions 至少给:
    • Contents: Read and write
  3. 生成后复制保存(只显示一次)

2) 在源码仓库添加 Secrets

源码仓库 → Settings → Secrets and variables → Actions → New repository secret:

  • PAGES_TOKEN:填写上一步生成的 PAT

3) 添加工作流文件

在源码仓库创建文件:.github/workflows/deploy-to-pages-repo.yml

如果你使用 pnpm(推荐锁定依赖,构建更可复现),可以用下面这个版本(把仓库名替换成你自己的):

yaml 复制代码
name: Build and Deploy Hexo

on:
  push:
    branches:
      - main
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: hexo-deploy
  cancel-in-progress: true

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout source repo
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20.19.0"

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
          run_install: false

      - name: Get pnpm store directory
        id: pnpm-cache
        shell: bash
        run: |
          echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT

      - name: Setup pnpm cache
        uses: actions/cache@v4
        with:
          path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Build site
        run: |
          pnpm run clean
          pnpm run build

      - name: Deploy to Pages repo
        uses: peaceiris/actions-gh-pages@v4
        with:
          personal_token: ${{ secrets.PAGES_TOKEN }}  #.后面的值要与你设置的Token名词保持一致
          external_repository: {你的公开仓库地址}
          publish_branch: main
          publish_dir: ./public
          commit_message: "deploy: ${{ github.sha }}"

说明:

  • concurrency 用来避免连续 push 时部署互相覆盖
  • publish_dir 指向 Hexo 生成目录 public
  • cache: pnpm + --frozen-lockfile 用来保证依赖安装更稳定

Pages 仓库设置检查

<你的用户名>.github.io 仓库里确认:

  • Settings → Pages
    • Source:Deploy from a branch
    • Branch:与你工作流推送的分支一致(上面示例是 main/(root)

第一次部署通常需要等待一会儿生效,地址是:

  • https://<你的用户名>.github.io

主题管理:如何"直接链接主题仓库"并保持可更新

很多 Hexo 主题都有独立仓库。你当然可以"链接它"来及时更新,但要先想清楚:你是否需要改主题源码,或者仅仅是想跟进主题版本。

方案 A:通过包管理器安装主题(更省心,推荐)

不少主题提供了 npm/pnpm 安装方式(主题包名与主题名不一定相同,以主题文档为准):

bash 复制代码
pnpm add <theme-package>

然后在 Hexo 的 _config.yml 里设置:

yml 复制代码
theme: <theme-name>

更新主题也很简单:只要更新依赖并提交 pnpm-lock.yaml,CI 就能拿到同一版本的主题,且你可以随时升级:

bash 复制代码
pnpm update <theme-package>

适用场景:

  • 你希望主题跟随上游更新,但不直接改主题源码
  • 你更在意构建可复现(锁文件控制版本)

方案 B:用 Git submodule 链接主题仓库(可跟进上游,但要把 CI 配齐)

如果你想把主题代码放在 themes/ 目录下,同时又希望能"指向上游仓库",可以用 submodule(主题更新由你控制,避免每次构建都隐式漂移到最新提交):

bash 复制代码
git submodule add <theme-repo-url> themes/<theme-dir>

然后 _config.yml 里使用对应主题目录名:

yml 复制代码
theme: <theme-dir>

更新主题时:

bash 复制代码
git submodule update --remote --merge

但要注意:CI 必须把 submodule 一起拉下来,否则构建时主题目录会是空的/不完整,生成结果就会异常(后面会讲到 0KB 问题)。

方案 C:把主题源码直接提交到源码仓库(最直观、也最稳)

如果你频繁改主题源码,并且不想引入 submodule 复杂度,可以直接把 themes/<theme-dir> 当作普通目录提交到源码仓库:

  • 优点:CI 不需要任何 submodule 配置,最不容易踩坑
  • 缺点:跟进上游更新需要你手动合并/同步

日常发布流程

  1. 写文章:source/_posts/xxx.md
  2. 本地预览(可选):
bash 复制代码
pnpm run clean
pnpm run build
pnpm run server
  1. push 到源码仓库 main
  2. 等 Actions 跑完后访问站点即可

常见问题排查

Actions 成功但页面不更新

  • 检查 Pages 设置的分支/目录是否与工作流一致(例如 main/(root)
  • 检查 Pages 仓库是否收到最新提交(Actions 会推送一个 commit)
  • 等待 Pages 缓存刷新或清理浏览器缓存

部署报 403 / permission denied

  • PAT 权限不足:确认 token 对 Pages 仓库有 Contents 写权限
  • PAT 过期/撤销:重新生成并更新 PAGES_TOKEN
  • external_repository 写错:必须是 用户名/用户名.github.io

Actions 成功但页面白屏:Pages 仓库里很多 JS/HTML 变成 0KB

这个问题看起来像"部署把文件写坏了",但本质通常发生在更早的"构建阶段":

  • Hexo 构建时拿不到主题 layout(常见日志是大量 No layout: index.html 等)
  • 产物生成出来就是空/异常文件
  • 部署动作只是把 public/ 原样推送到 Pages 仓库,所以会看到 index.js 等文件是 0KB
根因(一个非常常见的坑)

主题目录 themes/xxx 被当成"独立 Git 仓库"或 "Git submodule(gitlink)"在管理,但 CI 并没有把主题内容拉下来:

  • checkout 没有开启 submodules
  • .gitmodules 缺失/不完整(例如 submodule 没有 url)

最终导致 CI 工作区里主题为空/不完整,Hexo 渲染时找不到 layout,生成就会异常。

修复方式(推荐顺序)
  1. 如果你不需要改主题源码:优先用"包管理器安装主题"(上面方案 A),最稳

  2. 如果你要改主题源码:

  • 不要把主题目录留成 "gitlink 子模块"但不配置 CI
  • 要么把主题作为普通目录提交到源码仓库(主题目录里不要有 .git
  • 要么继续用 submodule,但工作流必须拉取 submodule

以工作流为例,至少要做到两点:

(1) CI 在构建前确认主题存在(以 themes/<theme-dir> 为例)

yaml 复制代码
- name: Verify theme is present
  shell: bash
  run: |
    set -euo pipefail
    test -f themes/<theme-dir>/layout/layout.ejs
    test -f themes/<theme-dir>/layout/index.ejs

(2) CI 在部署前确认产物不为空(避免把空产物推到 Pages)

yaml 复制代码
- name: Verify build output is not empty
  shell: bash
  run: |
    set -euo pipefail
    test -f public/index.html
    test -s public/index.html
    zero_core_count=$(find public -type f \( -name '*.html' -o -name '*.js' -o -name '*.xml' -o -name '*.json' \) -size 0 -print | wc -l | tr -d ' ')
    if [ "${zero_core_count}" != "0" ]; then
      echo "Found ${zero_core_count} 0-byte core files (*.html/*.js/*.xml/*.json) under public/. Showing first 50:"
      find public -type f \( -name '*.html' -o -name '*.js' -o -name '*.xml' -o -name '*.json' \) -size 0 -print | head -n 50
      exit 1
    fi

如果你选择 submodule 方案,还需要在 checkout 时开启 submodules(否则主题仍然拉不下来):

yaml 复制代码
- uses: actions/checkout@v4
  with:
    submodules: recursive

引用

相关推荐
XD7429716362 小时前
科技早报晚报|2026年5月4日:Agent 的三件新基建——工作流桥接、增量记忆与本地深研,今天最值得跟进的 3 个机会
科技·github·开源项目·ai agent
lwf0061644 小时前
GitHub 项目托管与访问教程
github
Hommy884 小时前
【开源剪映小助手】媒体信息生成接口
开源·智能路由器·github·媒体·剪映小助手
CoderJia程序员甲5 小时前
GitHub 热榜项目 - 周榜(2026-05-10)
人工智能·ai·大模型·llm·github
牛奶咖啡135 小时前
CI/CD——在jenkins中使用pipeline方式自动化构建java项目jpress
ci/cd·自动化·jenkins·pipeline是什么·pipeline有啥用·pipeline适用场景·pipeline使用示例
2301_815279526 小时前
Z-BlogCMS安装教程详细版
github
DogDaoDao7 小时前
【GitHub】SuperClaude Framework深度解析:将Claude Code打造为专业开发平台的元编程配置框架
人工智能·深度学习·程序员·大模型·github·ai编程·claude
Harvy_没救了8 小时前
【AI Agent】Win11 系统 DeepSeek-TUI 实施方案总结
github·ai agent·deepseek
cong_8 小时前
狐蒂云🦊跑路我的摸鱼岛没了!
前端·后端·github
AC赳赳老秦8 小时前
故障自愈实战:用 OpenClaw 实现服务器日志自动化分析、根因定位、解决方案自动生成
大数据·运维·服务器·自动化·github·deepseek·openclaw