Hexo + NexT 博客搭建与自动化部署实践

用了几年 WordPress,最终把博客迁到了 Hexo + NexT 的纯静态方案。这篇文章把迁移和部署过程中的关键决策、配置方法和踩过的坑完整记录下来,方便同样想搭建个人博客的人参考。

为什么选静态博客

个人博客的核心诉求其实很简单:写得开心、打开得快、长期可维护。

  • WordPress 功能全,但需要 PHP + MySQL 运行时,日常要操心插件更新、备份和安全补丁;
  • 动态站每次访问都要查库渲染,首屏速度天然吃亏;
  • 静态站点生成器(SSG)把渲染放在构建期,线上只是一堆 HTML/CSS/JS,可以扔到任何 CDN 上,速度和稳定性都是顶级的。

Hexo 生态成熟、Node 编写插件丰富,NexT 主题配置项极其细致,最终选了这套组合。

Hexo + NexT 从本地写作到CDN 分发的部署链路

仓库结构

博客仓库保持”内容与配置分离”的经典结构:

1
2
3
4
5
6
7
8
9
10
11
12
zzWeiBlog/
├── _config.yml # Hexo 主配置(站点信息、目录、URL 规则)
├── _config.next.yml # NexT 主题配置(覆盖主题默认值)
├── package.json # 依赖与 npm scripts
├── scaffolds/ # 文章/页面模板
├── source/
│ ├── _posts/ # 文章
│ ├── _data/ # NexT 自定义注入(head/footer/body-end)
│ ├── images/ # 图片资源
│ └── ... # 自定义页面目录
├── .cnb.yml # CNB CI 配置(推送到 Gitee 镜像)
└── edgeone.json # EdgeOne Pages 配置(apex 域名跳转)

主题的安装方式:npm 而不是 git submodule

早期 Hexo 主题通常以 git submodule 放在 themes/ 目录,但 submodule 在 CI 环境里经常因为权限或递归克隆问题挂掉。更现代的做法是通过 npm 安装主题:

1
npm install hexo-theme-next

_config.yml 里照常写 theme: next。Hexo 的主题解析顺序是:先找 themes/next 目录,找不到就回退到 node_modules/hexo-theme-next。所以 themes/ 目录是空的完全正常,别被吓到。

主题配置不要直接改 node_modules 里的文件(升级即丢),NexT 支持在仓库根目录放一个 _config.next.yml 作为覆盖配置,所有想改的项照抄过去改值即可,主题本身的默认值保持不动,升级主题零冲突。

踩坑一:hexo-cli 缺失,CI 构建必挂

package.json 的 scripts 写的是 hexo generate,但 hexo 这个命令来自 hexo-cli 包。如果依赖里只装了 hexo 没装 hexo-cli,本地开发时因为全局恰好装过 CLI 能跑,一到 CI 的干净环境就 hexo: command not found,构建直接失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"scripts": {
"build": "hexo generate",
"clean": "hexo clean",
"server": "hexo server"
},
"dependencies": {
"hexo": "^8.1.1",
"hexo-cli": "^4.3.2",
"hexo-theme-next": "^8.29.0",
"hexo-generator-index": "^4.0.0",
"hexo-generator-archive": "^2.0.0",
"hexo-generator-category": "^2.0.0",
"hexo-generator-tag": "^2.0.0",
"hexo-generator-search": "^2.4.3",
"hexo-renderer-ejs": "^2.0.0",
"hexo-renderer-marked": "^7.0.1",
"hexo-renderer-stylus": "^3.0.1",
"hexo-server": "^3.0.0",
"hexo-neat": "^1.0.9"
}
}

原则:凡是 scripts 里用到的命令,必须出现在 dependencies 里,不要依赖 CI 镜像碰巧预装。

踩坑二:博客列表页没有入口

_config.yml 里我把文章列表配置到了独立路径 /journal/(个人博客站点出于备案考虑,菜单文案用”日志”而不是”博客”,URL 同步规避):

1
2
3
4
index_generator:
path: '/journal/'
per_page: 10
order_by: -date

结果主题菜单里这一项一开始没接线——文章列表页生成了却没有任何入口能点进去,访客只能从归档页翻。这种”配置了但没接线”的问题很隐蔽,改主题配置时一定要检查菜单:

1
2
3
4
5
6
7
8
# _config.next.yml
menu:
home: / || fa fa-home
journal: /journal/ || fa fa-stream
archives: /archives/ || fa fa-archive
categories: /categories/ || fa fa-th
tags: /tags/ || fa fa-tags
links: /links/ || fa fa-link

菜单的 key(如 journal)是翻译键,显示文字在 source/_data/languages.yml 里覆盖:

1
2
3
zh-CN:
menu:
journal: 日志

踩坑三:迁移遗留的死资产

从旧主题迁过来时,source/ 里躺着一批没人引用的文件:

  • 一个 1MB 的 echarts.min.js——旧主题某个图表页面用的,页面早没了;
  • 一份 2018 年的阿里云图标字体 CSS——页脚一个小图标在用它,字体文件放在第三方 CDN 上,图标时好时坏;
  • 若干只被已删除页面引用的 jQuery 副本。

清理原则:构建产物里每一个字节都要有出处。用 grep -rn 扫一遍引用,没人引用的直接删;页脚那个图标换成 Font Awesome 内置图标(NexT 自带 FA,零额外请求),旋转动画用 styles.styl 里的 keyframes 重写,外部字体依赖彻底消灭。

分类与标签体系

文章多起来之后,分类比标签更重要。Hexo 的 categories 支持数组表达层级:

1
2
3
4
5
6
7
8
9
10
---
title: HarmonyOS NEXT 服务卡片实践
date: 2026-10-15 09:00:00
categories:
- 技术分享 # 一级分类
- 移动开发 # 二级分类
tags:
- HarmonyOS
- ArkTS
---

目前的分类树:

1
2
3
4
5
6
技术分享
├── 建站运维
├── 后端开发
├── 移动开发
├── 机器人开发
└── 网络技术

标签只用来做细粒度检索,不要膨胀,一篇文章三五个足矣。

自动化部署链路

1
2
3
本地写作 → git push → CNB(唯一真源)
├─ CI: 镜像同步 → Gitee(只读备份)
└─ EdgeOne Pages: 检测到 push → npm install → hexo generate → CDN 分发

几个关键点:

  1. 单一真源:CNB 为主仓库,Gitee 只做单向镜像,两边同时写必然打架;
  2. CI 里做构建:EdgeOne Pages 识别到 package.json 会自动执行安装和构建,本地只管写 Markdown;
  3. apex 域名跳转:edgeone.json 里把裸域 301 到 www,避免 CDN 回源时分流;
  4. package-lock.json 要不要提交:我选择忽略,让 CI 每次按语义化版本范围安装,换取依赖的小版本自动更新;追求严格可复现的团队则应该提交 lock 文件。

隐私红线

博客仓库是公开的,以下几类东西永远不要进仓库:

  • 旧平台导出的个人数据文件(比如某个博客平台的 .xls 导出,里面全是历史文章和账号信息);
  • 一次性迁移脚本,跑完即弃,它们的”历史价值”为零,”泄露价值”为正;
  • 任何含内网地址、Token、Cookie 的配置文件。

迁移工具的正确归宿是本地跑完就删,或者放进 .gitignore。

小结

静态博客的维护成本比想象中低,但”配置没接线”和”CI 环境缺依赖”这两类问题最耗时。把 hexo-cli 补全、菜单接好、死资产清干净,这套 Hexo + NexT + EdgeOne 的组合可以稳定服役很多年。