使用Hexo、GitHub和Cloudflare Pages搭建个人博客
前言
前段时间重新整理了一下博客的主题和部署环境。之前写文章最麻烦的地方并不是Markdown本身,而是每换一台电脑就要重新配置环境,写完后还要手动生成网页、上传文件,时间久了就很容易懒得更新😓。
所以这一次我使用Hexo + Butterfly + GitHub + Cloudflare Pages重新搭了一套博客。平时只需要在本地写Markdown并推送到GitHub,Cloudflare就会自动安装依赖、生成静态网页并部署到全球CDN,自定义域名和HTTPS也可以一起解决。
整个流程可以简单理解为:
1 | 本地Markdown文章 |
这篇文就以本博客当前使用的配置为例,从零介绍如何搭建出同样的博客,并把其中比较容易踩坑的地方记录下来。整体配置预计需要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还可以生成独立的预览地址。
准备开发环境
需要提前准备:
- 一台安装了Git和Node.js的电脑
- 一个GitHub账号
- 一个Cloudflare账号
- 一个域名,可选,没有域名也可以使用免费的
pages.dev地址
本博客当前使用Hexo 8。根据Hexo官方文档,Hexo 8要求Node.js版本不低于20.19.0,所以不建议继续使用Node.js 18之类的旧环境。安装完成后可以检查版本:
1 | node --version |
只要Node.js满足版本要求即可,不必和我的本地版本完全相同。
创建Hexo博客
初始化工程
先安装Hexo命令行工具,再初始化一个博客目录:
1 | npm install -g hexo-cli |
初始化后主要会得到下面这些文件和目录:
1 | my-blog/ |
其中source/_posts保存真正需要长期维护的文章,Hexo构建后会在public目录生成完整网站。public只是构建产物,随时可以重新生成,因此不需要提交到GitHub。
安装Butterfly主题
Butterfly可以通过Git或npm安装。本博客选择npm方式,这样主题版本会被package.json和package-lock.json记录下来,Cloudflare构建时也能自动安装,不需要额外处理Git子模块。
1 | npm install hexo-theme-butterfly@^5.7.0 |
然后修改根目录下的_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 | title: Your Blog |
url一定要改成最终访问博客的完整地址。如果暂时没有自定义域名,可以先填写Cloudflare分配的https://项目名.pages.dev。这个值不正确时,站内链接、分享链接和搜索引擎收录地址都有可能出问题。
本博客在package.json中定义了这些命令:
1 | { |
其中Cloudflare实际使用的是npm run build。deploy命令在本方案中不会用到,因为发布工作已经交给Cloudflare Pages。
Butterfly主题配置
主题可配置项很多,没有必要一开始全部修改。建议先关注导航栏、首页、文章目录、代码块、字体和图片灯箱。本博客使用的一部分配置如下:
1 | code_blocks: |
先保证网站能正常生成,再逐项调整主题,出现问题时会容易定位很多。尤其是YAML对缩进十分敏感,只能使用空格,不要混入Tab。
添加自定义CSS
如果主题配置还不能满足需求,可以在source/css/custom.css中编写样式。例如调整正文和标题大小:
1 | #article-container { |
然后在_config.butterfly.yml中注入这个文件:
1 | inject: |
放在source下的普通静态文件会在构建时复制到public,所以网页最终可以通过/css/custom.css访问。
编写第一篇文章
使用下面的命令创建文章:
1 | hexo new "我的第一篇博客" |
Hexo会根据scaffolds/post.md,在source/_posts中生成Markdown文件。为了让首页摘要、分类、标签和主题选项正常显示,可以使用下面的Front Matter:
1 |
|
两个---之间是文章元信息,后面直接使用Markdown写正文即可。图片既可以使用图床链接,也可以放到source/img目录后使用站内绝对路径:
1 |  |
如果图片很多,建议提前想好图片存储方案。静态博客本身没有后台上传接口,文章引用的本地图片也必须一起提交到GitHub,否则Cloudflare构建出来的页面会找不到它们。
本地预览和构建
写完配置后先不要急着部署,执行本地预览:
1 | npm run server |
浏览器打开终端提示的地址,通常是http://localhost:4000。确认首页、文章、代码块和图片都正常后,再执行一次和云端相同的正式构建:
1 | npm run clean |
构建成功后,生成的静态网站会出现在public目录。Cloudflare Pages最终发布的也正是这个目录。
将源码推送到GitHub
先在GitHub创建一个空仓库。已有本地工程时,建议不要在远程仓库中预先生成README或.gitignore,避免第一次推送时出现两段无关历史。
博客根目录的.gitignore至少应包含:
1 | node_modules/ |
然后在博客目录执行:
1 | git init |
需要提交package-lock.json。它能让本地和Cloudflare安装到一致的依赖版本,减少某个包自动升级后突然构建失败的情况。相反,node_modules和public都不应提交。
使用Cloudflare Pages自动部署
连接GitHub仓库
根据Cloudflare的Hexo部署文档,进入Cloudflare控制台后进行以下操作:
- 打开Workers & Pages,选择创建应用
- 进入Pages,选择导入现有Git仓库
- 授权Cloudflare访问GitHub,并选择刚才创建的博客仓库
- 填写构建配置并保存部署
最关键的配置只有下面几项:
1 | Production branch:main |
由于本博客使用Hexo 8,还需要在Pages项目的环境变量中设置一个满足要求的Node.js版本:
1 | NODE_VERSION=24 |
也可以选择其他受支持且不低于20.19.0的版本。第一次部署时,Cloudflare会拉取仓库、安装package.json中的依赖、执行npm run build,最后把public目录发布出去。成功后会得到一个类似项目名.pages.dev的地址。
到这里CI/CD就已经完成了。以后每次更新博客只需要:
1 | git add . |
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 | npm run clean |
Hexo的缓存文件db.json可能保留旧结果,所以排查配置问题时先清理缓存是个好习惯。如果线上部署已经成功但仍显示旧内容,再检查Cloudflare构建对应的提交是否正确。
YAML配置导致构建失败
YAML的层级完全由缩进决定。不要使用Tab,同一层级保持相同空格数,带冒号或特殊字符的长文本最好加引号。改动_config.yml或_config.butterfly.yml后,都应该先在本地完整构建一次。
总结
总的来说,这套方案把博客拆成了很清晰的三部分:本地只负责写Markdown,GitHub负责保存和管理源码,Cloudflare Pages负责构建、托管和分发。没有服务器、数据库和手动上传文件的维护成本,博客依然能保留完整的主题定制能力。
对我来说,最大的提升是发布文章终于变成了一个很自然的Git工作流:写完、预览、提交、推送,然后等一会儿就能在线访问。以后迁移电脑时也只需要克隆仓库并执行npm install,整套环境就回来了🙂。
如果只是想拥有一个长期稳定、成本很低、内容完全由自己掌控的技术博客,Hexo + GitHub + Cloudflare Pages依然是一套相当省心的组合。