前言

前段时间重新整理了一下博客的主题和部署环境。之前写文章最麻烦的地方并不是Markdown本身,而是每换一台电脑就要重新配置环境,写完后还要手动生成网页、上传文件,时间久了就很容易懒得更新😓。

所以这一次我使用Hexo + Butterfly + GitHub + Cloudflare Pages重新搭了一套博客。平时只需要在本地写Markdown并推送到GitHub,Cloudflare就会自动安装依赖、生成静态网页并部署到全球CDN,自定义域名和HTTPS也可以一起解决。

整个流程可以简单理解为:

1
2
3
4
5
6
7
8
9
10
本地Markdown文章
│ git push
▼
GitHub源码仓库
│ 自动触发构建
▼
Cloudflare Pages ── npm run build ──► public静态文件
│
▼
pages.dev域名 / 自定义域名

这篇文就以本博客当前使用的配置为例,从零介绍如何搭建出同样的博客,并把其中比较容易踩坑的地方记录下来。整体配置预计需要30分钟,不包括注册账号、下载Node.js和域名解析等待的时间。

技术方案说明

这套方案中各部分的分工如下:

  • Hexo:读取Markdown文章和配置文件,生成HTML、CSS和JavaScript静态文件
  • Butterfly:负责博客的页面布局、代码块、目录、深色模式等主题功能
  • GitHub:保存博客源码、文章和依赖版本,同时提供版本管理
  • Cloudflare Pages:拉取GitHub仓库,执行构建命令,并把生成的静态文件发布到CDN
  • 自定义域名:作为博客的固定入口,Cloudflare同时负责HTTPS证书

需要注意,这里并不是用GitHub Pages托管网页,也没有必要自己编写GitHub Actions。GitHub只保存源码,真正的CI/CD由Cloudflare Pages的Git集成完成。每次向生产分支推送提交,Pages都会自动重新构建;其他分支和Pull Request还可以生成独立的预览地址。

准备开发环境

需要提前准备:

  1. 一台安装了Git和Node.js的电脑
  2. 一个GitHub账号
  3. 一个Cloudflare账号
  4. 一个域名,可选,没有域名也可以使用免费的pages.dev地址

本博客当前使用Hexo 8。根据Hexo官方文档,Hexo 8要求Node.js版本不低于20.19.0,所以不建议继续使用Node.js 18之类的旧环境。安装完成后可以检查版本:

1
2
3
node --version
npm --version
git --version

只要Node.js满足版本要求即可,不必和我的本地版本完全相同。

创建Hexo博客

初始化工程

先安装Hexo命令行工具,再初始化一个博客目录:

1
2
3
4
npm install -g hexo-cli
hexo init my-blog
cd my-blog
npm install

初始化后主要会得到下面这些文件和目录:

1
2
3
4
5
6
7
8
my-blog/
├── _config.yml # Hexo站点配置
├── package.json # 构建命令和依赖
├── package-lock.json # 锁定实际依赖版本
├── scaffolds/ # 新文章模板
├── source/
│ └── _posts/ # Markdown文章
└── themes/ # 使用Git方式安装的主题

其中source/_posts保存真正需要长期维护的文章,Hexo构建后会在public目录生成完整网站。public只是构建产物,随时可以重新生成,因此不需要提交到GitHub。

安装Butterfly主题

Butterfly可以通过Git或npm安装。本博客选择npm方式,这样主题版本会被package.json和package-lock.json记录下来,Cloudflare构建时也能自动安装,不需要额外处理Git子模块。

1
2
npm install hexo-theme-butterfly@^5.7.0
npm install hexo-renderer-pug hexo-renderer-stylus --save

然后修改根目录下的_config.yml:

1
theme: butterfly

按照Butterfly官方的建议,最好不要直接修改node_modules里的主题文件。将主题的默认配置复制到博客根目录,之后只维护自己的覆盖配置:

1
Copy-Item .\node_modules\hexo-theme-butterfly\_config.yml .\_config.butterfly.yml

Hexo会自动合并主题默认配置和_config.butterfly.yml,同名选项以后者为准。这样升级主题时,不会因为重新安装依赖而丢失自己的设置。

配置站点信息

Hexo基础配置

打开根目录的_config.yml,先完成最关键的站点信息。下面是一份精简后的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
title: Your Blog
subtitle: 'Hardware · Software · Life'
description: '个人技术博客'
author: your-name
language: zh-CN

url: https://blog.example.com
permalink: posts/:title/

source_dir: source
public_dir: public

theme: butterfly

url一定要改成最终访问博客的完整地址。如果暂时没有自定义域名,可以先填写Cloudflare分配的https://项目名.pages.dev。这个值不正确时,站内链接、分享链接和搜索引擎收录地址都有可能出问题。

本博客在package.json中定义了这些命令:

1
2
3
4
5
6
7
8
{
"scripts": {
"build": "hexo generate",
"clean": "hexo clean",
"deploy": "hexo deploy",
"server": "hexo server"
}
}

其中Cloudflare实际使用的是npm run build。deploy命令在本方案中不会用到,因为发布工作已经交给Cloudflare Pages。

Butterfly主题配置

主题可配置项很多,没有必要一开始全部修改。建议先关注导航栏、首页、文章目录、代码块、字体和图片灯箱。本博客使用的一部分配置如下:

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
code_blocks:
theme: darker
macStyle: true
copy: true
language: true
fullpage: true

index_top_img_height: 460px
index_layout: 3

toc:
post: true
number: true
scroll_percent: true

post_copyright:
enable: true
license: CC BY-NC-SA 4.0
license_url: https://creativecommons.org/licenses/by-nc-sa/4.0/

font:
global_font_size: 16px
code_font_size: 14px
font_family: '-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif'
code_font_family: '"Cascadia Code", "JetBrains Mono", Consolas, monospace'

lightbox: fancybox

先保证网站能正常生成,再逐项调整主题,出现问题时会容易定位很多。尤其是YAML对缩进十分敏感,只能使用空格,不要混入Tab。

添加自定义CSS

如果主题配置还不能满足需求,可以在source/css/custom.css中编写样式。例如调整正文和标题大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#article-container {
font-size: 18px;
line-height: 1.85;
}

#article-container h2 {
font-size: 26px;
margin-top: 1.8em;
}

@media screen and (max-width: 768px) {
#article-container {
font-size: 16.5px;
line-height: 1.8;
}
}

然后在_config.butterfly.yml中注入这个文件:

1
2
3
4
inject:
head:
- <link rel="stylesheet" href="/css/custom.css">
bottom:

放在source下的普通静态文件会在构建时复制到public,所以网页最终可以通过/css/custom.css访问。

编写第一篇文章

使用下面的命令创建文章:

1
hexo new "我的第一篇博客"

Hexo会根据scaffolds/post.md,在source/_posts中生成Markdown文件。为了让首页摘要、分类、标签和主题选项正常显示,可以使用下面的Front Matter:

1
2
3
4
5
6
7
8
9
10
11
---
title: 我的第一篇博客
date: 2026/8/16 20:00:00
cover: false
mathjax: false
summary: 这是文章摘要
categories: Note
tags:
- 总结
- Hexo
---

两个---之间是文章元信息,后面直接使用Markdown写正文即可。图片既可以使用图床链接,也可以放到source/img目录后使用站内绝对路径:

1
![图片说明](/img/example.png)

如果图片很多,建议提前想好图片存储方案。静态博客本身没有后台上传接口,文章引用的本地图片也必须一起提交到GitHub,否则Cloudflare构建出来的页面会找不到它们。

本地预览和构建

写完配置后先不要急着部署,执行本地预览:

1
npm run server

浏览器打开终端提示的地址,通常是http://localhost:4000。确认首页、文章、代码块和图片都正常后,再执行一次和云端相同的正式构建:

1
2
npm run clean
npm run build

构建成功后,生成的静态网站会出现在public目录。Cloudflare Pages最终发布的也正是这个目录。

将源码推送到GitHub

先在GitHub创建一个空仓库。已有本地工程时,建议不要在远程仓库中预先生成README或.gitignore,避免第一次推送时出现两段无关历史。

博客根目录的.gitignore至少应包含:

1
2
3
4
5
node_modules/
public/
db.json
*.log
.deploy*/

然后在博客目录执行:

1
2
3
4
5
6
git init
git add .
git commit -m "init blog"
git branch -M main
git remote add origin https://github.com/你的用户名/你的仓库名.git
git push -u origin main

需要提交package-lock.json。它能让本地和Cloudflare安装到一致的依赖版本,减少某个包自动升级后突然构建失败的情况。相反,node_modules和public都不应提交。

使用Cloudflare Pages自动部署

连接GitHub仓库

根据Cloudflare的Hexo部署文档,进入Cloudflare控制台后进行以下操作:

  1. 打开Workers & Pages,选择创建应用
  2. 进入Pages,选择导入现有Git仓库
  3. 授权Cloudflare访问GitHub,并选择刚才创建的博客仓库
  4. 填写构建配置并保存部署

最关键的配置只有下面几项:

1
2
3
4
Production branch:main
Build command:npm run build
Build output directory:public
Root directory:留空,也就是仓库根目录

由于本博客使用Hexo 8,还需要在Pages项目的环境变量中设置一个满足要求的Node.js版本:

1
NODE_VERSION=24

也可以选择其他受支持且不低于20.19.0的版本。第一次部署时,Cloudflare会拉取仓库、安装package.json中的依赖、执行npm run build,最后把public目录发布出去。成功后会得到一个类似项目名.pages.dev的地址。

到这里CI/CD就已经完成了。以后每次更新博客只需要:

1
2
3
git add .
git commit -m "新增文章"
git push

Cloudflare检测到main分支变化后会自动开始新的构建和部署,不需要在本地执行上传命令,也不需要把任何Cloudflare Token放进GitHub仓库。

绑定自定义域名

打开Pages项目的Custom domains,选择添加自定义域名,例如:

1
blog.example.com

如果域名本来就在当前Cloudflare账号中管理,确认后Cloudflare会自动添加所需的DNS记录并申请HTTPS证书。如果DNS由其他平台管理,则需要按照控制台提示添加CNAME,将博客子域名指向项目名.pages.dev。

需要注意,必须先在Pages项目的Custom domains中关联域名,不能只手动添加一条CNAME。Cloudflare的自定义域名文档明确说明,跳过关联步骤可能导致域名解析失败。

最后别忘了同步修改Hexo配置:

1
url: https://blog.example.com

修改后提交并推送,让Cloudflare重新构建一次,站内生成的链接就会全部切换到自定义域名。

常见问题

Cloudflare构建时提示Node.js版本错误

先检查构建日志中的Node.js版本,再确认Pages环境变量设置了NODE_VERSION。Hexo 8至少需要20.19.0,本地能运行不代表Cloudflare默认环境一定相同。

部署成功但打开页面是404

检查Build output directory是否填写为public。source里放的是Markdown源码,不能直接作为网站发布。也可以在本地运行npm run build,确认public/index.html确实已经生成。

本地正常,Cloudflare缺少主题或渲染器

检查需要的包是否记录在package.json的dependencies中,并确认package-lock.json已经提交。只在本地全局安装的工具不会自动出现在Cloudflare环境里。

修改配置后页面没有变化

先执行一次:

1
2
npm run clean
npm run build

Hexo的缓存文件db.json可能保留旧结果,所以排查配置问题时先清理缓存是个好习惯。如果线上部署已经成功但仍显示旧内容,再检查Cloudflare构建对应的提交是否正确。

YAML配置导致构建失败

YAML的层级完全由缩进决定。不要使用Tab,同一层级保持相同空格数,带冒号或特殊字符的长文本最好加引号。改动_config.yml或_config.butterfly.yml后,都应该先在本地完整构建一次。

总结

总的来说,这套方案把博客拆成了很清晰的三部分:本地只负责写Markdown,GitHub负责保存和管理源码,Cloudflare Pages负责构建、托管和分发。没有服务器、数据库和手动上传文件的维护成本,博客依然能保留完整的主题定制能力。

对我来说,最大的提升是发布文章终于变成了一个很自然的Git工作流:写完、预览、提交、推送,然后等一会儿就能在线访问。以后迁移电脑时也只需要克隆仓库并执行npm install,整套环境就回来了🙂。

如果只是想拥有一个长期稳定、成本很低、内容完全由自己掌控的技术博客,Hexo + GitHub + Cloudflare Pages依然是一套相当省心的组合。