最近想起来 Butterfly 主题的架构,其实支持将说说页面与主网站完全分离部署。这个想法源于一个令人头疼的问题:我的网站 Git 提交记录已经长得离谱,每次查看历史都要滚动很久,这严重影响了版本管理的效率。更重要的是,我的 GitHub 账号能登录了——尽管不是原来那个,但这个更古老的账号反而让我找回了早期的开发记忆。这意味着可以重新启用 GitHub Actions 来自动构建网站,这样一来,我只需要推送网站的源代码到仓库,Actions 就会自动完成构建和部署,完全不用像之前在 Cloudflare Pages 上那样,每次更新都要手动上传成千上万个静态文件,那种重复劳动简直是对生命的浪费。

在配置 GitHub Actions 的过程中,我遇到了一个有趣的问题:官方的 Actions 模板库里面居然没有为 Hexo 准备的现成模板,但 Next.js 的模板却赫然在列。本着“长得像就能用”的技术宅直觉,我对这个 Next.js 模板进行了暴力改造。我把所有出现 Next.js 的地方全部替换成了 Hexo,然后仔细调整了缓存路径的配置,将原本缓存改为指向 node_modules.gitnext build 改成 hexo generate,最后把输出目录 .next 改成了 public。经过这一连串的修改,原本为 React 框架设计的模板就变成了一个专为 Hexo 量身定制的构建流程:

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
# Sample workflow for building and deploying a Hexo site to GitHub Pages
#
# To get started with Hexo see: https://hexo.io/docs/
#
name: Deploy Hexo site to Pages

on:
# Runs on pushes targeting the default branch
push:
branches: ["main"]

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false

jobs:
# Build job
build:
runs-on: ubuntu-latest
env:
TZ: Asia/Shanghai
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Detect package manager
id: detect-package-manager
run: |
if [ -f "${{ github.workspace }}/yarn.lock" ]; then
echo "manager=yarn" >> $GITHUB_OUTPUT
echo "command=install" >> $GITHUB_OUTPUT
echo "runner=yarn" >> $GITHUB_OUTPUT
exit 0
elif [ -f "${{ github.workspace }}/package.json" ]; then
echo "manager=npm" >> $GITHUB_OUTPUT
echo "command=ci" >> $GITHUB_OUTPUT
echo "runner=npx --no-install" >> $GITHUB_OUTPUT
exit 0
else
echo "Unable to determine package manager"
exit 1
fi
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "25"
cache: ${{ steps.detect-package-manager.outputs.manager }}
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Restore cache
uses: actions/cache@v4
with:
path: |
node_modules
.git
# Generate a new cache whenever packages change.
key: ${{ runner.os }}-hexo-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-hexo-
- name: Install dependencies
run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
- name: Build with Hexo
run: ${{ steps.detect-package-manager.outputs.runner }} hexo generate
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./public

# Deployment job
deploy:
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

现在的流程已经完全跑通了,每当我把修改推送到 main 分支,GitHub Actions 就会自动执行从依赖安装到静态文件生成的全套流程。工作流设置了并发控制防止同时在跑的构建任务相互干扰,还配置了缓存机制来加速后续构建。更贴心的是,工作流还自动处理了时区设置,确保生成的文章时间戳准确无误。最后,构建好的 public 目录会被打包成 Artifact 并自动部署到 GitHub Pages 上。这套流程不仅解放了我的双手,还让网站的更新变得像写博客一样自然——我只用专注内容创作,剩下的一切都交给自动化来完成。

搞定部署流程后,我转头处理说说数据的格式转换问题。Butterfly 主题的说说功能原本读取的是 source/_data/shuoshuo.yml 文件,而文档里写的是 JSON 文件,这意味着我需要把它变成独立的 JSON 格式以便远程加载。我决定用 Python 来完成这个转换任务,首先在项目根目录创建了一个名为 essay 的文件夹并初始化成仓库,用来单独存放分离后的说说数据。接着搭建 Python 虚拟环境——创建独立环境,再激活,这样做是为了防止依赖污染全局 Python 环境:

1
2
python3 -m venv .venv
source .venv/bin/activate

我写的 Python 脚本利用了 PyYAML 库来解析 YAML 文件,代码逻辑非常清晰:读取 shuoshuo.yml 后遍历每一条说说数据。这里有个关键细节——YAML 中的日期会被解析成 Python 的 datetime 对象,而 JSON 标准要求日期必须是字符串格式,所以我把它格式化成了可读的时间字符串。同时我还对内容字段做了处理,去掉多余的换行符,确保 JSON 文件干净整洁。最后输出格式化的 JSON,通过设置 ensure_ascii=False 来保留中文原貌,indent=2 让文件具有良好的可读性:

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
from datetime import datetime

import yaml
import json


def convert_yaml_to_json(yaml_path, json_path):
with open(yaml_path, 'r', encoding='utf-8') as f:
yaml_data = yaml.safe_load(f)

converted_data = []
for item in yaml_data:
date_value = item['date']
if isinstance(date_value, datetime):
date_value = date_value.strftime('%Y-%m-%d %H:%M:%S')

converted_item = {
"date": date_value,
"content": item['content'].rstrip() if isinstance(item['content'], str) else item['content'],
"key": str(item['key']),
"tags": item.get('tags', [])
}
converted_data.append(converted_item)

with open(json_path, 'w', encoding='utf-8') as f:
json.dump(converted_data, f, ensure_ascii=False, indent=2)


if __name__ == "__main__":
yaml_file = "./source/_data/shuoshuo.yml"
json_file = "./essay/essay.json"
convert_yaml_to_json(yaml_file, json_file)
print("Conversion completed!")

这个脚本本质上是一次性工具,所以转换完成后我就可以毫不犹豫地删掉虚拟环境文件夹和源代码了。但等等——还有一个重要的手动步骤不能忘记:修改说说页面的 Frontmatter 配置。我需要将原来的 shuoshuo.md 改成远程拉取模式,添加一个 shuoshuo_url 字段指向我即将部署的 JSON 文件地址:

1
2
3
4
5
6
7
---
title: 说说
date: 2026-04-03 22:29:16
type: shuoshuo
shuoshuo_url: https://essay.example.com/essay.json # 这里填的是说说文件的链接!
---

这样一来,说说页面在渲染时就会通过网络请求获取最新的数据,而不是依赖项目本地的YAML文件,真正实现了内容的分离管理。最后我踩到了一个典型的路径陷阱:我最初创建的 GitHub 仓库名正好叫 shuoshuo,由于我在 GitHub Pages 配置过域名的缘故,Pages 替换了原本网站指向的路径 /shuoshuo/,和我网站说说的路径 /shuoshuo/ 产生了冲突,导致浏览器不停地跳转,我还傻傻地反馈问题给主题官方。解决方案其实很简单,改一个不不重名的仓库名即可,比如推送到一个新的 GitHub 仓库 essay,最后在该仓库启用页面功能。从此以后,说说数据和主网站可以独立更新、独立构建、互不干扰,整个系统的维护成本大大降低了。