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

引用

相关推荐
冬奇Lab2 小时前
AI Native 时代的 CI/CD:从“手工流水线”到“智能驾驶舱”的范式演进
人工智能·ci/cd
徐小夕2 小时前
PDF无限制预览!Jit-Viewer V1.5.0开源文档预览神器正式发布
前端·vue.js·github
没有口袋啦4 小时前
《基于 GitOps 理念的企业级自动化 CI/CD 流水线》
阿里云·ci/cd·云原生·自动化·k8s
起个名字总是说已存在6 小时前
github开源AI技能:Awesome DESIGN.md让页面设计无限可能
人工智能·开源·github
zhensherlock7 小时前
Protocol Launcher 系列:Overcast 一键订阅播客
前端·javascript·typescript·node.js·自动化·github·js
第一程序员8 小时前
Python数据结构与算法:非科班转码者的学习指南
python·github
SUNNY_SHUN8 小时前
清华团队提出TFA-Net,用模板特征聚合破解工业异常检测中的“捷径学习“难题
人工智能·学习·视觉检测·github
CeshirenTester8 小时前
GitHub变了:私有仓库也要“喂AI”?开发者该怎么应对
人工智能·github