建站一周年:从 NotionNext 迁移到 Hexo 安知鱼

时间过得很快,从去年四月建站到现在,整整一年了。

这一年来,网站经历了从零搭建、持续日更、产品化探索等多个阶段。
但在一周年之际,我做了一个重要决定:从 NotionNext 迁移到 Hexo 安知鱼主题。

这篇文章,就是这次迁移的完整记录。

🧭 背景:为什么要迁移?

NotionNext 之痛

我最初选择 NotionNext 作为博客框架,核心原因是内容管理使用 Notion,写作体验非常好。
但使用一年下来,最大的痛点也逐渐暴露:

Notion 上游 API 频繁变更,导致经常无法构建发布。

对于一个博客来说,稳定是第一位的
你无法接受写了一篇文章,发布时发现构建失败了,而且你无法控制什么时候能修复——因为这取决于 Notion 的 API 变更和 NotionNext 项目的适配速度。

这种不确定性对于一个需要持续输出的博客来说是致命的。

为什么是 Hexo 安知鱼?

选定 Hexo + 安知鱼主题,主要有两个原因:

  1. Hexo 是老朋友。我很早以前就使用过 Hexo,对其工作机制非常熟悉,静态站点生成器的稳定性和可控性是动态依赖 Notion API 的方案无法比拟的。

  2. 安知鱼与 Heo 主题几乎一模一样。之前 NotionNext 使用的是 Heo 主题风格,安知鱼主题的设计风格与 Heo 几乎一致,这意味着我只需要少量的定制改造就能还原之前的视觉效果,迁移成本极低。

📦 从 Notion 迁移数据

迁移的第一步,是把 Notion 中的所有文章数据导出来。

1. 导出 Markdown

Notion 支持将页面导出为 Markdown 格式。但直接导出有几个问题:

  • 只导出了 Markdown,缺少封面图
  • 缺少 Hexo 需要的 front-matter 元数据
  • AI 摘要信息丢失,需从之前的缓存中读取

2. API 获取封面链接

Notion 导出的 Markdown 不包含封面图信息,需要通过 Notion API 补全:

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
import requests

NOTION_TOKEN = "your-integration-token"
DATABASE_ID = "your-database-id"

headers = {
"Authorization": f"Bearer {NOTION_TOKEN}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json",
}

response = requests.post(
f"https://api.notion.com/v1/databases/{DATABASE_ID}/query",
headers=headers,
json={"page_size": 100},
)

for page in response.json()["results"]:
cover = page.get("cover", {})
if cover.get("type") == "external":
cover_url = cover["external"]["url"]
elif cover.get("type") == "file":
cover_url = cover["file"]["url"]
print(f"Title: {page['properties']['title']['title'][0]['plain_text']}")
print(f"Cover: {cover_url}")

通过 API 遍历数据库中所有文章,提取封面链接并下载到自己的 青萍 AI 图床

3. front-matter 格式转换

NotionNext 的 front-matter 格式与 Hexo 不同,需要转换。核心字段映射如下:

NotionNext Hexo 说明
title title 文章标题
date date 发布日期
summary description 文章摘要
category categories 分类(Hexo 支持层级)
tags tags 标签
slug permalink URL 别名
cover cover 封面图

转换脚本会将每篇文章的 front-matter 从 NotionNext 格式转为 Hexo 格式,同时处理分类和标签的映射关系。

经过以上三个步骤,二百多篇文章就从 Notion 成功迁移到了 Hexo 的 source/_posts/ 目录。

🔧 Hexo 搭建与配置

数据准备好后,就是搭建 Hexo 站点和配置安知鱼主题。

1. 主题下载

1
2
3
4
5
6
# 初始化 Hexo 站点
hexo init blog
cd blog

# 下载安知鱼主题
git clone https://github.com/anzhiyu-c/hexo-theme-anzhiyu.git themes/anzhiyu

_config.yml 中启用主题:

1
theme: anzhiyu

2. 基础配置修改

安知鱼主题的配置项非常多,主要集中在 _config.anzhiyu.yml 中。

导航菜单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
menu:
首页: /
产品:
青萍AI图床: https://img.lusyoe.com || fa-solid fa-image
青萍AI视频: https://video.lusyoe.com || fa-solid fa-video
文章:
归档: /archives || anzhiyu-icon-box-archive
分类: /categories || anzhiyu-icon-shapes
标签: /tags || anzhiyu-icon-tags
友链:
友人帐: /links || anzhiyu-icon-link
朋友圈: /friends || anzhiyu-icon-artstation
留言板: /comments || anzhiyu-icon-envelope
我的:
音乐馆: /music || anzhiyu-icon-music
关于:
关于本人: /about || anzhiyu-icon-paper-plane
日常瞬间: /moments || anzhiyu-icon-bolt

分页配置

1
2
3
4
5
index_generator:
per_page: 12

archive_generator:
per_page: 36

页脚、站长信息

配置页脚的社交链接、版权信息、建站时间等,确保与旧站一致。

封面和字体

将全站字体和封面图替换为自己的 CDN 资源,提升加载速度。

3. 扩展页面

安知鱼主题支持多种扩展页面,我逐一搭建了以下页面:

  • links:友情链接页面,展示友链卡片
  • about:关于页面,包含个人信息、技能点、联系方式
  • moments:日常瞬间页面,类似朋友圈的短内容
  • friends:朋友圈页面,聚合友站最新动态
  • comments:留言板页面,接入 Twikoo 评论系统
  • music:音乐馆页面
  • categories:分类总览页面

每个页面都是独立的 Markdown 文件,放在 source/ 对应目录下。

4. 主题定制

安知鱼主题虽然开箱即用很棒,但要完美还原之前的 Heo 风格,还需要一些定制改造。

导航菜单改为垂直

安知鱼默认的导航菜单是水平排列的,让子菜单更多时比较难看,并且之前的 Heo 风格是垂直分组展示。
修改了 nav.pug 模板和 nav.styl 样式,将菜单改为垂直分组展示,每个一级菜单作为一个分组,二级菜单垂直排列。

右侧面板添加板块

在文章详情页的右侧面板中,增加了以下信息卡片:

  • 最新瞬间:展示最近的日常瞬间动态
  • 最近查看:展示最近更新的文章
  • 最新评论:接入 Twikoo 展示最新评论
  • 标签:展示标签云,方便快速导航

这些卡片通过修改 Pug 模板和 Stylus 样式实现,部分通过自定义 widget 组件完成。

站点统计添加 vercount

之前在 NotionNext 中就使用了自己部署的 vercount,迁移到 Hexo 后继续使用:

1
2
3
vercount:
enable: true
server: https://vercount.lusyoe.com

vercount 与不蒜子相比,数据更稳定,加载更快,而且支持自托管。

友链卡片定制

安知鱼默认的友链卡片样式比较简单。为了更好地展示友链信息,我新增了 flexcard-large 样式:

  • 更大的头像尺寸
  • 更清晰的信息层级
  • 更好的视觉效果

添加 RSS 集成

RSS 订阅是博客的基础功能,通过 hexo-generator-feed 插件实现:

1
2
3
4
5
feed:
enable: true
type: atom
path: rss/feed.xml
limit: 20

为保持兼容,修改 path

Algolia 搜索添加 slug 支持兼容

之前 NotionNext 使用 Algolia 搜索,文章 URL 格式为 /article/slug。迁移到 Hexo 后,需要确保搜索结果中的链接格式一致。

修改了搜索索引的生成逻辑,确保 slug 字段正确映射到 permalink,保持搜索结果的 URL 兼容性。

首页文章卡片添加简短摘要信息显示

安知鱼主题默认的首页文章卡片只显示标题、分类、标签和发布日期,缺少文章的摘要预览。

为了提升首页的信息密度,让访客快速了解每篇文章的内容,我对卡片布局进行了调整:

  • 将文章描述(description)内容显示在标题和标签之间。
  • 配置 index_post_content.method: 2,优先使用 front-matter 中的 description 字段,没有则自动截取正文
  • 标签和发布日期下移至卡片底部,与描述内容形成清晰的信息层级

文章详情页面左侧添加文章目录

安知鱼主题默认将文章目录(TOC)放在右侧面板中,但右侧面板已经包含了最新瞬间、最近查看、最新评论、标签云等多个信息卡片,信息密度非常高。
而且默认的 TOC 在滚动时会隐藏,实用性大打折扣,尤其是对于长篇技术文章。

为了提升阅读体验,我在文章详情页左侧新增了独立的 sticky 定位目录组件。

这样右侧面板专注于站点信息展示,左侧目录专注于文章导航,互不干扰。

5. URL 路径兼容

这是一个容易被忽视但非常重要的细节。

之前 NotionNext 的文章 URL 格式是 /article/slug,没有 .html 后缀也没有尾部斜杠。而 Hexo 默认生成的路径会在后面自动带上 .html/,比如 /article/slug.html/article/slug/

网站已经运营了一年,搜索引擎已经积累了大量的索引,如果 URL 格式变化会导致:

  • 搜索引擎收录的旧链接全部 404
  • 外部引用和友链失效
  • SEO 权重归零

解决方案分三步:

第一步,Hexo permalink 配置

_config.yml 中手动设置 permalink 格式,让生成的文件路径包含 .html

1
2
3
4
5
permalink: article/:slug.html
permalink_defaults: article/:slug.html
pretty_urls:
trailing_index: false
trailing_html: false

但 Hexo 默认的 :slug 是基于目录名 + 文章标题生成的,与我之前的 slug 格式不一致。
因此通过 Hexo 的 post_permalink 过滤器,从每篇文章的 front-matter 中读取自定义的 slug 字段来生成 permalink:

1
2
3
4
5
6
7
8
9
10
11
hexo.extend.filter.register("post_permalink", function (data) {
var raw = data.raw;
if (!raw) return data;
var fm = raw.match(/^---[\r\n]+([\s\S]*?)[\r\n]+---/);
if (!fm) return data;
var slugMatch = fm[1].match(/^slug:\s*(.+?)\s*$/m);
if (slugMatch && slugMatch[1]) {
data.__permalink = "article/" + slugMatch[1] + ".html";
}
return data;
}, 1);

这样 Hexo 生成的静态文件就会使用文章 front-matter 中指定的 slug,与旧站的 URL 路径完全一致。

第二步,Nginx try_files 配置

但还有一个问题:旧站的 URL 是 /article/slug(无 .html),而实际文件是 /article/slug.html。需要在 Nginx 层面做兼容,让无后缀的请求也能正确访问:

1
2
3
4
5
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri.html $uri/index.html =404;
}

关键在于 try_files 指令的 $uri.html——当请求 /article/slug 时,Nginx 会依次尝试:

  1. /article/slug(原路径,不存在)
  2. /article/slug.html(拼接 .html,命中!)
  3. /article/slug/index.html(目录下的 index)
  4. 返回 404

这样无论访问 /article/slug 还是 /article/slug.html 都能正常访问,完美保持了与旧站的 URL 兼容性。

6. 静态资源,全站 CDN 切换

最后一步是将所有静态资源切换到自己的 CDN,涵盖 CSS/JS 资源、字体文件、图片资源(favicon、logo 等)以及第三方库(Twikoo、vercount 等前端脚本)。

我编写了一个脚本,自动扫描主题配置中引用的所有远程 CDN 资源并批量下载到自己的 青萍 AI 图床,然后将主题配置中的 CDN 地址统一替换为 cdn.lusyoe.com

整个过程一键完成,无需手动逐个替换,大幅提升了资源加载速度。

🚀 部署

部署方案沿用了之前的 Docker + Nginx 方式:

1
2
3
4
5
6
7
8
9
10
11
FROM nginx:alpine

RUN rm -rf /usr/share/nginx/html/* && \
rm -rf /etc/nginx/conf.d/* && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

COPY ./public/ /usr/share/nginx/html/
COPY ./nginx.conf /etc/nginx/nginx.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

📊 迁移对比

对比项 NotionNext Hexo 安知鱼
内容管理 Notion Markdown 文件
构建稳定性 依赖 Notion API 本地构建,完全可控
主题风格 Heo 安知鱼(几乎一致)
部署方式 Docker + Nginx Docker + Nginx
搜索 Algolia Algolia(兼容)
评论 Twikoo Twikoo
计数 vercount vercount
CDN 阿里云 ESA 阿里云 ESA
写作体验 Notion 编辑器 Markdown 编辑器
构建速度 较慢(依赖 API) 秒级

💭 写在最后

建站一周年,从 NotionNext 迁移到 Hexo,这次迁移总共只花了 2天 时间,表面上看是技术栈的变更,但本质上是对稳定性的追求。

NotionNext 是一个非常优秀的项目,它让我快速搭建了博客,也让我体验到了 Notion 写作的便利。

但当博客发展到一定阶段,稳定可控方便快捷更重要。

迁移过程虽然有些繁琐,但结果令人满意。Hexo 的静态生成完全可控,不再受上游 API 变更的影响。

安知鱼主题的视觉效果与之前的 Heo 风格几乎一致,甚至略超一筹,迁移成本远低于预期。

如果你也在使用 NotionNext 并且遇到了类似的稳定性问题,或者正在考虑搭建博客,希望这篇文章能给你一些参考。