基于 Hexo 8 + NexT 8 的静态博客全新迁移过程记录

早在很多年前,我基于 Hexo + NexT 搭建了这个静态博客。它经历过多次小修小补,也在 2020 年做过一次比较完整的升级。几年之后再回头看,Hexo、Node.js、NexT 的版本体系和推荐安装方式都已经发生了明显变化:旧项目还能作为资料库存在,但继续在旧目录里原地升级,风险会越来越高。

所以这次采用了一个更稳妥的方案:不在旧项目上原地升级,而是创建一个全新的 Hexo 8 + NexT 8 项目,再把仍然有效的配置、文章和资源选择性迁移过来。这样做的好处是,最终得到的是一个干净、可维护、可复现的新项目,而不是一个背着多年历史包袱的“能跑就行”的项目。

本文记录完整迁移过程,包含方案选择、环境准备、项目初始化、配置迁移、主题迁移、内容迁移、构建验证、踩坑处理和后续建议。

最终效果与迁移结果

本次迁移完成后,新博客具备如下特征:

  1. 使用 hexo@8.1.2hexo-theme-next@8.27.0
  2. 使用 npm 安装 NexT 主题,而不是复制旧主题源码
  3. 使用根目录 _config.next.yml 管理 NexT 8 的主题配置
  4. 保留旧博客的标题、作者、语言、域名、永久链接格式、头像、菜单和文章资源目录策略
  5. 迁移全部旧文章、页面和资源
  6. 删除或关闭旧的第三方统计、搜索、评论、QQ 404 等非必要能力
  7. 迁移后继续完成一轮偏审美和阅读体验的配置调优:Gemini 布局、跟随系统深色模式、关闭页面入场动画、优化 TOC、字体栈和代码块主题
  8. 本地构建验证通过:初始迁移生成 317 files,补写迁移复盘和完成配置调优后最新生成 321 files
  9. 未执行线上部署、远程推送或任何需要凭证的外部操作

迁移后的内容规模如下:

项目 数量
文章 Markdown 42
非文章 Markdown 页面 4
source 文件 143
文章资源目录 28

其中初始迁移阶段是 41 篇旧文章、142 个 source 文件;补写本文后,文章数变为 42,source 文件数变为 143。旧 404 页面同时存在 Markdown 与 HTML 两份实现,并且都依赖外部 QQ/qzone 脚本,本次迁移将它重写成根级 source/404.md,生成 public/404.html,因此旧内容本身仍比原项目更干净,也更符合静态托管平台的 404 约定。

本次迁移方案

本次采用的迁移方案如下:

  1. 先只读盘点旧项目,记录配置、依赖和内容数量
  2. 在空的新目录中初始化全新 Hexo 项目
  3. 锁定 Hexo / NexT 版本,提交 package-lock.json
  4. 迁移站点级配置,例如标题、作者、语言、URL、永久链接和文章资源目录策略
  5. 按 NexT 8 的配置格式重新编写 _config.next.yml
  6. 迁移文章、页面、头像、favicon、CNAME 和文章资源目录
  7. 替换旧 404 页面中的第三方外部脚本
  8. 运行本地生成命令,发现问题后修复
  9. 编写迁移文档,记录命令、配置取舍、内容清单和验证结果
  10. 用 git 分阶段记录每个有意义的迁移步骤

这个方案比“直接复制旧项目然后升级依赖”更慢一点,但好处非常明显:

  • 依赖更干净
  • 配置更可读
  • 主题升级路径更正规
  • 迁移历史可审计
  • 后续维护成本更低

环境与版本准备

迁移时的本地环境如下:

1
2
node -v
npm -v

实际使用版本:

1
2
node v24.15.0
npm 11.12.1

Hexo 8 对 Node.js 有最低版本要求,本机 Node 版本满足要求。迁移前还查询了当前 Hexo 与 NexT 的 npm 版本:

1
2
3
npm view hexo version engines --json
npm view hexo-theme-next version --json
npm view hexo-cli version --json

最终锁定的关键版本如下:

组件 版本
Hexo 8.1.2
Hexo CLI 4.3.2
NexT 8.27.0
Node.js 要求 >=20.19.0

这里没有直接使用一个永远漂移的 latest 状态,而是把最终安装结果写入 package-lock.json。以后如果要升级,可以从一个明确的版本点出发。

旧项目基线盘点

旧项目路径:

1
cd /home/lml/projects/bitkylin-blog-old

首先确认旧项目工作区状态:

1
git status --short --branch

然后盘点内容规模:

1
2
3
4
find source/_posts -maxdepth 1 -type f -name '*.md' | wc -l
find source -path source/_posts -prune -o -type f -name '*.md' -print | wc -l
find source -type f | wc -l
find source/_posts -mindepth 1 -maxdepth 1 -type d | wc -l

盘点结果:

项目 旧项目数量
文章 Markdown 41
非文章 Markdown 页面 4
source 文件 143
文章资源目录 28

_config.yml 中需要保留的站点身份主要包括:

1
2
3
4
5
6
7
8
9
title: 比特麒麟
subtitle: https://github.com/bitkylin
description: Without the continuous bitter cold, there be no fragrant plum blossom ~ ~ 靡不有初,鲜克有终。
author: 雪中亮「123lml123」
language: zh-CN
url: http://bitky.cc
permalink: :year/:month/:day/:title/
post_asset_folder: true
theme: next

这些字段属于博客的“身份信息”,如果随意改变,会影响旧链接、文章资源路径和站点识别度,所以应当保留。

初始化全新 Hexo 项目

新项目路径:

1
cd /home/lml/projects/bitkylin-blog

初始化脚手架:

1
2
3
4
npx hexo-cli@4.3.2 init .
npm install --save-exact hexo@8.1.2 hexo-theme-next@8.27.0
git init
npm install

初始化后检查 Hexo 是否可用:

1
2
npx hexo version
npm ls --depth=0

最终依赖中保留了 Hexo 基础生成器、渲染器、server 和 NexT 主题:

1
2
3
4
5
6
7
8
9
10
hexo@8.1.2
hexo-theme-next@8.27.0
hexo-generator-archive@2.0.0
hexo-generator-category@2.0.0
hexo-generator-index@4.0.0
hexo-generator-tag@2.0.0
hexo-renderer-ejs@2.0.0
hexo-renderer-marked@7.0.1
hexo-renderer-stylus@3.0.1
hexo-server@3.0.0

这里做了几个重要取舍:

  1. 移除了 starter 自带的 hexo-theme-landscape
  2. 不安装旧项目中的部署插件
  3. 不保留任何部署 npm script
  4. 增加 .nvmrcpackage.jsonengines.node
  5. public/node_modules/db.json.deploy_git/ 等生成产物加入 .gitignore

迁移站点级配置

新项目的 _config.yml 不是从旧文件整段复制,而是以 Hexo 8 的默认配置为基础,只迁移仍然有效且必要的字段。

核心配置初始迁移时保留了旧博客身份,后续又按当前审美做了一次轻量整理。当前关键配置如下:

1
2
3
4
5
6
7
8
9
10
11
title: 比特麒麟
subtitle: 靡不有初,鲜克有终
description: Without the continuous bitter cold, there be no fragrant plum blossom
keywords: Java, Linux, Docker, Spring Boot, Netty, Kafka, MongoDB, JavaScript, Vue.js, HTML5, CSS3, Node.js
author: 雪中亮「123lml123」
language: zh-CN
timezone: 'Asia/Shanghai'
url: http://bitky.cc
permalink: :year/:month/:day/:title/
post_asset_folder: true
theme: next

这里的变化不是为了 SEO 生硬堆词,而是把副标题、描述和关键词改成更符合个人气质与真实技术栈的表达。subtitle 使用“靡不有初,鲜克有终”保留中文长期主义气质;description 保留英文句子本身,不再把中英文格言混在一行;keywords 则体现 Java、Linux、Docker、Spring Boot 以及部分前端技术栈。

其中 post_asset_folder: true 很关键。旧文章中有大量与文章同名的资源目录,关闭它会让图片迁移变得麻烦,也容易出现资源路径错位。

部署配置没有迁移到 active 配置中。原因很简单:本次迁移的目标是本地完成项目升级和内容迁移,不执行线上发布。部署仓库和分支属于上线阶段的事情,应当单独规划,不能在迁移阶段混入外部副作用。

迁移 NexT 主题配置

旧项目里有一份 NexT 主题配置备份:

1
tools/next主题备份/_config.yml

但旧配置不能直接复制到新项目中。NexT 8 推荐使用 npm 安装主题,并通过根目录 _config.next.yml 管理自定义配置。因此本次新建了:

1
_config.next.yml

保留的主题个性化包括:

能力 决策
Gemini 布局 从初始 Pisces 调整为 Gemini,整体更现代
深色模式 使用 darkmode: true,不使用 lightdark 路径
页面入场动画 motion.enable: false,避免加载时拖沓
菜单 保留 home / about / tags / categories / archives
侧边栏 保留左侧栏
头像 保留 /uploads/avatar.jpg,并启用圆角
社交链接 保留 GitHub、Email、简书、知乎、个人资料
TOC 保留,不显示编号,长标题换行,最大深度 4
代码块 使用 Atom One 明暗主题、mac 风格复制按钮、显示语言名
字体 使用本地系统字体栈和现代代码字体栈,不依赖外部 CDN
返回顶部 保留
阅读进度条 保留
GitHub banner 保留
Fancybox 开启图片预览,保留 mediumzoom 关闭
Creative Commons 保留 by-nc-sa

保持关闭或舍弃的能力包括:

能力 决策 原因
busuanzi 统计 关闭 旧第三方统计,本次不恢复
本地搜索 关闭 需要额外 generator,旧项目也未启用
评论系统 不配置 旧配置多为占位或关闭
打赏、日历、聊天等 不配置 非本次迁移目标
旧主题源码 不复制 避免未来升级困难

这一步的核心原则是:保留博客的味道,不继承旧配置债务

迁移后一轮页面风格调优

初始迁移完成后,我又根据实际预览效果做了一轮更偏审美和阅读体验的配置调整。这一轮调整并不是恢复旧主题的全部细节,而是围绕“现代、克制、清爽、适合技术长文阅读”的方向收敛配置。

当前 _config.next.yml 中最关键的变化如下:

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
scheme: Gemini
darkmode: true
lightdark:
enable: false
check_supports: true

motion:
enable: false

toc:
enable: true
number: false
wrap: true
expand_all: false
max_depth: 4

codeblock:
theme:
light: atom-one-light
dark: atom-one-dark
copy_button:
enable: true
style: mac
fold:
enable: false
height: 500
language: true

fancybox: true
mediumzoom: false
lazyload: true

这里有几个值得记录的取舍:

  1. 页面加载动画关闭,但页脚爱心跳动保留。前者会影响进入页面的节奏,后者只是一个轻量点缀。
  2. 深色模式走 darkmode: true,而不是 lightdark.enable: true。实际排查时发现,lightdark 可以让页面主体变暗,但代码块深色主题仍可能不切换;NexT 的代码高亮深色 CSS 与 darkmode 路径更匹配。
  3. 代码主题使用 atom-one-light / atom-one-dark,比默认主题更适合技术文章。
  4. TOC 最大深度从 6 收敛到 4,并允许长标题换行,避免侧边栏目录过碎。
  5. 先不写自定义 Stylus 样式。NexT 官方后续可能继续优化默认样式,过早自定义会增加升级冲突。

字体配置也单独整理了一次,原则是本地系统字体优先,不依赖 Google Fonts 或其它外部 CDN:

1
2
3
4
5
6
7
8
font:
enable: true
global:
external: false
family: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, "Noto Sans", "Noto Sans CJK SC", "Source Han Sans SC", "Microsoft YaHei UI", "Microsoft YaHei", "Helvetica Neue", Arial'
codes:
external: false
family: '"JetBrains Mono", "JetBrainsMono Nerd Font", "JetBrainsMono Nerd Font Mono", "Source Code Pro", "SauceCodePro Nerd Font", "SourceCodePro Nerd Font", "Fira Code", "Cascadia Code", "Cascadia Mono", "Ubuntu Mono", Menlo, Monaco'

全局字体覆盖 macOS、Ubuntu、Windows 和常见中文字体;代码字体优先使用 JetBrains Mono、Source Code Pro,并兼容本机安装的 Nerd Font 补丁字体。Nerd Fonts 只作为增强项,不作为公开博客的硬依赖,因为访客本机未安装时浏览器会自然 fallback。

迁移文章、页面和资源

内容迁移主要执行如下操作:

1
2
3
4
5
6
7
cp -a /home/lml/projects/bitkylin-blog-old/source/_posts/. source/_posts/
cp -a /home/lml/projects/bitkylin-blog-old/source/about source/about
cp -a /home/lml/projects/bitkylin-blog-old/source/categories source/categories
cp -a /home/lml/projects/bitkylin-blog-old/source/tags source/tags
cp -a /home/lml/projects/bitkylin-blog-old/source/uploads source/uploads
cp -a /home/lml/projects/bitkylin-blog-old/source/favicon.ico source/favicon.ico
cp -a /home/lml/projects/bitkylin-blog-old/source/CNAME source/CNAME

这里保留了:

  1. 全部 41 篇文章
  2. aboutcategoriestags 页面
  3. 头像文件
  4. favicon
  5. CNAME 域名身份文件
  6. 28 个文章资源目录

404 页面没有原样复制。它依赖 QQ/qzone 外部脚本和样式,本次改写为一个根级 Markdown 页面,并通过 permalink: /404.html 生成静态托管平台通常识别的 public/404.html

1
2
3
4
5
6
7
8
9
10
11
12
---
title: 404
date: 2021-05-15 17:28:07
comments: false
permalink: /404.html
---

# 404:页面未找到

抱歉,你访问的页面不存在或已经迁移。

[返回比特麒麟首页](/)

这样做的好处是:

  • 没有外部脚本依赖
  • 不会引入旧第三方服务
  • 页面语义更清楚
  • 后续维护更简单

第一次构建验证

内容迁移完成后,执行:

1
2
npx hexo clean
npx hexo generate

本次比较幸运,完整生成一次通过,初始迁移阶段最终输出:

1
317 files generated

后续补写迁移复盘文章、完成主题风格调优和字体配置后,再次执行生成,最新输出为:

1
321 files generated

这说明旧文章的 front matter、Markdown、资源路径与 Hexo 8 / NexT 8 的组合基本兼容,后续主题配置调整也没有破坏构建。

如果以后迁移类似项目,构建失败时可以优先排查:

  1. front matter 中未转义的冒号或特殊字符
  2. 非法日期格式
  3. 文章资源目录与 post_asset_folder 不一致
  4. 图片文件名大小写问题
  5. 旧主题标签或旧 HTML 混排语法
  6. 第三方脚本在新版渲染器下的兼容性

审计与安全检查

为了确认迁移结果不是“看起来能跑”,还做了几类审计。

检查是否安装部署插件:

1
npm ls hexo-deployer-git

结果为空,说明没有安装。

检查是否有 deploy npm script:

1
node -e "const p=require('./package.json'); if (p.scripts?.deploy) throw new Error('deploy script exists'); console.log('no deploy script')"

检查生成产物是否入库:

1
2
3
git ls-files public
git ls-files node_modules
git ls-files db.json

这些命令都没有输出,说明生成产物没有被 git 跟踪。

检查 404 页面是否仍包含旧外部脚本:

1
git grep -n -E "qzone|qq.com|search_children" -- source/404.md || true

结果无输出。

本地还启动过 Hexo server 做路由冒烟验证,以下路径均返回 200:

1
2
3
4
5
6
7
/
/archives/
/categories/
/tags/
/about/
/404.html
/2021/02/18/仿照Kafka从零开始自实现MQ/

Git 提交记录

本次迁移按阶段提交,方便后续回看和回滚:

Commit 说明
e597abe 建立可复现的现代 Hexo 迁移基线
751e381 迁移站点身份以保留博客可识别性
71a2cee 按 NexT v8 方式复刻兼容主题个性化
2960a0d 迁移博客内容与资源并记录取舍
4878b44 消除旧文中的可执行部署命令示例
9f9b18a 记录迁移过程以支持中文展示交付
d81cd5f 记录 Hexo NexT 全新迁移复盘
1be5d2f 沉淀博客审美偏好以稳定后续调优

我比较推荐这种迁移方式:每一步都可以独立解释,每个 commit 都有明确目的。迁移不是一把梭,而是可以复盘、可以验证、可以回滚。

和 2020 年升级方式的区别

2020 年那次升级,整体思路是“创建新脚手架 + 复制旧配置和主题 + 迁移 source”。这在当时是可行的,因为主题和脚手架变化没有现在这么大。

这次迁移做了几个变化:

  1. 不再复制 NexT 主题源码,而是使用 npm 包
  2. 不再机械复制主题配置,而是按 NexT 8 的配置项重新映射
  3. 不再保留部署插件和部署配置
  4. 不再恢复旧第三方统计、搜索、评论和 QQ 404
  5. package-lock.json 锁定版本
  6. 用中文迁移文档记录每个阶段的命令、数据变化和取舍

如果说 2020 年的升级重点是“让旧博客跟上新版本”,那么这次的重点是“把博客迁移到一个可长期维护的新基线”。

踩坑与经验

1. 不要直接复制旧主题配置

旧 NexT 配置文件很长,里面既有真正使用的配置,也有大量默认值、注释和第三方服务占位。直接复制的短期收益很高,但长期会留下大量不可解释的配置债务。

更好的做法是:

  1. 打开新版 NexT 默认配置
  2. 找出旧配置中真正启用的能力
  3. 只把仍然兼容且仍然想保留的 override 写入 _config.next.yml
  4. 把舍弃项写进迁移文档

2. 文章资源目录要优先保护

旧文章中包含大量图片。如果 post_asset_folder 设置不正确,构建也许可以通过,但页面中的图片会出问题。因此这类配置不应随便改。

3. 404 页面要特别检查

很多旧博客会复制一些外部 404 模板,看起来漂亮,但几年后这些外部脚本可能已经不再稳定,也可能不符合当前的隐私和维护要求。迁移时最好改成本地静态页面。

4. 迁移文档不是额外负担

本次单独维护了一份迁移审计记录,记录版本、命令、commit、配置取舍、内容计数、功能变化和验证结果。它的价值不只在“交付好看”,更在于以后再次升级时能知道当时为什么这么做。

后续建议

本次迁移没有执行线上部署。如果后续要真正上线,建议单独做一个部署阶段,至少确认:

  1. GitHub Pages 仓库和分支策略
  2. CNAME 与 HTTPS 配置
  3. 是否恢复部署插件
  4. 是否需要 CI 自动构建
  5. 是否需要搜索、评论或统计功能

这些都不应该混在迁移阶段一次性完成。迁移阶段最重要的是先得到一个干净、稳定、可生成的新博客。

总结

这次迁移的核心体会是:静态博客升级不难,难的是克制。旧项目中有很多“当年能用”的配置和脚本,但它们未必适合继续保留。真正稳妥的迁移,不是把旧目录搬到新目录,而是重新判断每一项配置和功能是否仍然值得存在。

最终结果是,一个基于 Hexo 8 + NexT 8 的全新博客项目已经建立完成,旧文章和主要身份信息得到保留,旧第三方服务和部署副作用被移除,本地构建和路由验证均通过。对于一个多年未维护的静态博客来说,这样的迁移方式比原地升级更安全,也更适合长期维护。

参考资料

  1. Hexo 官方文档
  2. NexT 主题文档