---
title: 写在迁移之后：从 Hexo 到 Astro
date: 2026-05-19
modified: 2026-05-19
url: https://yuuu.org/post/hexo-to-astro-after-migration
categories: []
tags: []
description: "把博客从 Hexo 迁到 Astro Typography 之后，才发现自己的博客其实不需要很多东西，也是趁着这个时候，对博客进行了一次破坏性升级。"
---
这三天，一直在用 AI 帮我迁移整个博客。迁移的时候，看着 AI 在干活，我就在旁边心神放空地看着它干活。

回想起前几年，我还在想，如果日后网站添加了很多的东西，那么岂不是要花费好久好久的时间才能完全迁移到另外一个地方，这可能要花费不少人力。

结果这几天，我看着 AI 自己一点一点地把数据和很多自定义的东西都从 Hexo 搬到 Astro，有现成的就直接引入、安装、运行；没有的，就自己写个大差不差的版本。看到这里总感觉有点感叹——想不到时代发展这么快。一想到十年前我的电脑还只有 4G 内存，浏览网页的时候还要注意不能超过十个网页，而家里二十年前的电子东西到如今只剩一个 DVD 机器还能运作。现在我却免费使用着由一个个数据中心支撑起来的大语言模型，总感觉有点唏嘘。

好了，先不说这么多了。下面说说这次博客更改之后的情况。

---

## 删除的

### hexo-abbrlink

首先是一切的万恶之源 **hexo-abbrlink**！

我两天前写了一篇博文说明这个问题（见 [关于 abbrlink 的一些见解](/post/about-abbrlink)）。对于这个插件，我只能说：「快拿掉！」。

之前 AI 在迁移的时候专门写一个 Astro 的路由来应付这些。但是于我而言，在新的框架里居然还要做一条能让旧时代活下去的船，是无法想象的。明明 Hexo 早就能自己写 `permalink`，居然还要用这个；之前的我真是没仔细钻研 Hexo 就开始部署网站，这是我的失误。

于我而言，我都花时间来换框架了，也不在乎这点破坏性的更新了：**弄了一个 `redirects.json`，直接把旧链接全部做成 301 重定向，并且日后全部都使用 slug，自己写。**

实现上并不复杂。根目录维护一份映射表，再在 `astro.config.ts` 里交给 Astro 的 `redirects` 选项：

```json
{
  "/post/16107.html": "/post/hello-world",
  "/post/37582.html": "/post/new-laptop",
  "/post/10005.html": "/post/about-abbrlink"
}
```

```ts
const redirectsStr = fs.readFileSync(path.resolve('./redirects.json'), 'utf-8')
const redirects = JSON.parse(redirectsStr)

export default defineConfig({
  site: themeConfig.site.website,
  redirects,
  trailingSlash: 'never',
  // ...
})
```

新文章不再算 abbrlink 哈希，日后路由自然回落到文件名或者 frontmatter 里的 `slug`。

### 字体

汉字博大精深源远流长……后边省略赞美。总之时代让我们有了很多字，也让字体文件变得非常大。新站在 Cloudflare 测试时，字体拖慢了太多太多；几次重复测试综合下来，首屏常常要下载 1 MB 以上的字体。我这里有一张饼图，可以很好地显示之前的博客的惨状；光是字体就有 1 MB，后面还有一堆第三方 JS 和其他 CSS，总体积还要更大。之前一直都对自己的网站的速度非常自信，因为我可是非常的「保守」呢！我又不加 Live2d，又没有什么大红灯笼，没有没有播放器，樱花飘落什么的，真是干净！而且我自己在 Cloudflare 里面开了一堆优化选项，Cloudflare 大善人能帮我解决一堆问题，~~但是因为没用小黄云，所以什么都没生效~~。所以我的网站相较于别人，应当是非常的流畅对吧？

结果现在看来，简直就是在海底光缆里面运输💩，而且是臭不可闻的那种。

![饼图](https://cdn.yuuu.org/post-img/35/1.webp)

你可能会想：第三方 JS 可能有点难优化，但是为什么不先试试字体子集化、`unicode-range` 按需加载、动态子集化服务、格式优化成 woff2、上传国内 CDN 之类的操作来压缩字体？

其实我非常早之前就想到这个问题了，很早之前就发现 Google 那边在用 CSS 的 `unicode-range` 规则，于是也给我的站点的字体做上了同样处理的子集化。但全站使用霞鹜文楷，首页就要下载 32 个子集化的字体文件，共 1 MB 的字体：

![字体瀑布图](https://cdn.yuuu.org/post-img/35/2.webp)

对于中文字体分包的情况，对 **LXGW WenKai / 霞鹜文楷** 来说，光是 CSS 就有 226 KB。

```bash
#  cdn 里面的 result.css 大小
$ ls -lh *.css
-rw-r--r-- 1 li 197121 226K May 18 10:37 result.css
```

分包之后文件夹里有 310 个 woff2（除去四个 css、演示页等，顶层文件）：

```bash
$ find . -maxdepth 1 -type f | wc -l
314
```

也就是说，你要先下载一个 226 KB 的 CSS（可能更小，因为有 Brotli）。然后还要拉一群总大小大于 1 MB 的 32 个字体，然后才能开始渲染文本嘛，天哪。

综上所述，用这个实在是明显拖慢了速度。

所以我决定还是简单地来，在 `src/.config/user.ts` 里只保留系统字体栈：

```ts
export default {
  appearance: {
    locale: 'zh-cn',
    fonts: {
      header:
        '"Noto Serif SC","Source Han Serif SC","Source Han Serif TC","Songti SC",Georgia,"Times New Roman",serif',
      ui: 'system-ui,-apple-system,"Segoe UI","Noto Sans SC","Source Han Sans SC","PingFang SC","Microsoft YaHei","Helvetica Neue",Arial,sans-serif',
    },
  },
}
```

如果你的设备里有我想要的，当然是最好；没有就直接用你的字体——总之就是要快。

对于那些没有中文字体的用户呢？

~~当然是放弃掉，手机里面没有中文字体，那么大概率也不是对中文感兴趣的人吧。~~

这次改完之后，首屏速度有没有「质变」我不好说，但至少不需要首次访问的时候要下载 1 MB 的零零散散的文件，也有利于减少我的 Vercel 的 Edge Requests。

### 番剧 / 追剧 / 书籍

遗憾的是，这些页面我都打算删了。

番剧和追剧都依托 [hexo-bilibili-bangumi](https://github.com/HCLonely/hexo-bilibili-bangumi)，Astro 没有同款；真要搓也能对接 Bangumi API 来实现一个差不多的，但我平时看番都懒得上 Bangumi 点一下，B 站版权又日益收紧，新番全部变成个人上传，开启了大盗版时代。也正因如此，我已经没法使用 B 站的番剧功能来看番了，那么还是直接舍弃掉吧。

我个人认为大家来博客大概还是看文章的；要看也应该对千字影评或读书心得更感兴趣，真有什么有意思的书本，我又读过，如果我有时间，我会直接写出来的。

这三个页面说白了是两年前想把博客做成「个人主页」时留下的，现在看来有点像是给别人展示用的，而不是真正为了记录什么。既然平时也没在维护，迁移的时候自然就顺手删掉了。

总之，只能说很有个性，但是除了摆出来让大家看一次，也没有别的益处了。

### 花里胡哨的装饰

#### 鼠标样式

![光标](https://cdn.yuuu.org/post-img/35/3.webp)

这个黑点当时花了好一些心思弄出来的。鼠标一会不动就会自动隐藏，滚动的时候也不显示，本意是让大家专注阅读。

然而据多人反馈，经常找不到鼠标。

而且我这半年看别人的博客就算有这个设计，也是不会隐藏的，而且更何况真要认真阅读的话，大家都懂得把鼠标移动到什么地方能方便阅读。

#### 点击特效

![点击特效](https://cdn.yuuu.org/post-img/35/4.webp)

优点：点点就有色彩光泽，很酷。

但实测一位好友的老爷机中的处理器 G1620 在多次点击的过程中不堪重负。

为了一些赛博苦行僧的阅读体验，也是为了博客的性能表现，还是删了吧。

#### 地图预览

我之前的网站右上角有一个地图预览，添加的原因是：拜托，这真的很酷的说。

不过现在把它关掉了，因为这个网站图在很多小屏幕电脑上会很占位置，以至于影响到了正文阅读，我们应当体贴那些只有小屏幕电脑的人的使用体验。

![地图预览](https://cdn.yuuu.org/post-img/35/5.webp)

顺带一提，f12 的图层功能就有一个好玩的预览图可以看。

![图层预览](https://cdn.yuuu.org/post-img/35/6.webp)

---

### 文章前序号

实不相瞒，我之前添加这个序号的时候，我最开始的设想是，我要用序号来激励自己，要保持更新！

后来结果也能预想到，那便是，我连博客都没打开，哪能看到那些序号？
所以，这次更新，我也打算将他们全部删掉。

而且，这么操作，我就可以往前面塞文章了。没错，我有很多文章其实早就写出来不好意思发出来罢了，完全可以直接偷偷塞前面去，就是不知道 rss 会不会抓取到？

## 新增的

### 404

想不到主题里居然没有像样的 404——不过考虑到我之前 Hexo-next 也没有，就让 AI 做了一个。

其实我自认为 404 还是有必要有的，对于 Vercel，一旦跳到 404，只能手动返回上一页，有些不便。

点击这里去看看 [404](/404)。

`src/pages/404.astro`：

```astro
<LayoutDefault>
  <div class="flex flex-col items-center justify-center py-24 gap-0 text-center">
    <p class="text-[120px] font-medium opacity-8 leading-none select-none font-serif animate-float">404</p>
    <p class="font-mono text-sm text-secondary mb-5">
      $ find / -name "this-page"<span class="animate-blink ..."></span>
    </p>
    <p class="text-base font-medium mb-1">页面不见了</p>
    <p class="text-sm text-secondary leading-relaxed mb-7">
      你来得比我的更新还勤快，<br />但这里什么都没有。
    </p>
    <a href="/" class="...">← 回首页</a>
  </div>
</LayoutDefault>

```

---

## 增补的

### 搜索

主题原本也没有站内搜索。之前 Hexo 用的是 Algolia——东西是好东西，但我博客文本量很少，**直接 `search.xml` + 前端 `fetch` 过滤** 就够用了，也省掉外部 JS。日后若 `search.xml` 太大，再考虑 Pagefind 或 Algolia。

```bash
$ ls -lh search.xml
-rw-r--r-- 1 li 197121 229K May 20 12:32 search.xml
```

构建索引：`src/pages/search.xml.ts` 在构建时把所有文章标题、URL、正文打进 XML：

```ts
export async function GET(_context: APIContext) {
  const posts = await getPosts()
  const xml = buildSearchXML(posts)
  return new Response(xml, {
    headers: {
      'Content-Type': 'application/xml; charset=utf-8',
      'Cache-Control': 'public, max-age=600',
    },
  })
}
```

搜索页 `src/pages/search.astro` 用内联脚本拉取并过滤：

```js
fetch('/search.xml')
  .then(res => res.text())
  .then((xmlText) => {
    const doc = new DOMParser().parseFromString(xmlText, 'application/xml')
    entries = Array.from(doc.querySelectorAll('entry')).map(entry => ({
      title: entry.querySelector('title')?.textContent || '',
      url: entry.querySelector('url')?.textContent || '',
      content: entry.querySelector('content')?.textContent || '',
    }))
  })
```

### 盘古之白（pangu）

我觉得盘古之白还是值得加的，中英文之间适当留白，读起来会舒服很多。

中英文、中文与数字之间自动加空格，用的是 [pangu.js](https://github.com/vinta/pangu.js)。在 `LayoutDefault.astro` 里全局初始化，并配合 Swup 在切页后重新执行：

```ts
import pangu from 'pangu'

const pg = pangu as any
pg.spacingPage()

document.addEventListener('swup:page:view', () => {
  initZoom()
  pg.spacingPage()
})
```

### 阅读进度条

这个功能对我来说算加分项，不是刚需。

`ReadingProgress.astro` 挂在默认布局顶部，监听 `main` 或 `document` 的滚动，用一条 fixed 的细线表示进度：

```astro
<div id="reading-progress" class="fixed top-0 left-0 h-0.5 bg-primary z-999 ..." style="width:0%"></div>
```

```js
function updateProgress() {
  const main = document.querySelector('main')
  if (main) {
    const { scrollTop, scrollHeight, clientHeight } = main
    const max = scrollHeight - clientHeight
    el.style.width = max > 0 ? `${(scrollTop / max) * 100}%` : '0%'
  }
  // ...
}
document.addEventListener('swup:page:view', () => {
  bindScroll()
})
```

### 信息卡（BioCard / AuthorCard）

主题没有「关于站主」式的头像区，于是加了 **BioCard**（关于页）和 **AuthorCard**（文章页底部）。逻辑类似，都是读 `themeConfig.site` 里的 `author` / `avatar` / `description`。

这个功能我仍保留意见，日后如果感觉太臃肿了，那就只保留在关于页面放一个就行了。

```astro
<img
  src={avatar}
  alt={author}
  data-fallback="/avatar.webp"
  onerror="this.src=this.dataset.fallback; this.onerror=null;"
  class="w-16 h-16 rounded-full object-cover shrink-0"
/>

```

关于页引入：`<BioCard />`。文章页在 `[...slug].astro` 里 `<AuthorCard />`。

### 阅读导航（目录 TOC）

文章顶部加了可折叠目录 `PostToc.astro`，用 Astro 渲染 Markdown 时自带的 `headings`，过滤掉 h1，只保留 h2–h3：

```astro
const toc = headings.filter((heading) => heading.depth > 1 && heading.depth < 4)

<details class="toc-container my-8 p-4 ...">
  <summary>Table of Contents</summary>
  <ul>
    {
      toc.map((heading) => (
        <li style={`margin-left: ${(heading.depth - 2) * 1.5}rem;`}>
          <a href={`#${heading.slug}`}>{heading.text}</a>
        </li>
      ))
    }
  </ul>
</details>

```

在 `src/pages/post/[...slug].astro` 里，目录插在正文最前面——主要是发现右侧塞不下了。塞了好几次都发现真的不懂得怎么塞，很容易因为用户小屏幕会出现错位问题。

同一块还顺带接了 **字数与阅读时间**：`remarkReadingTime` 插件在构建时写入 frontmatter，`PostMeta` 再展示：

```ts
// remarkReadingTime.ts
const chineseChars = textOnPage.match(/[\u4E00-\u9FA5]/g) || []
const englishWords
  = textOnPage.replace(/[\u4E00-\u9FA5]/g, ' ').match(/\b\w+\b/g) || []
const words = chineseChars.length + englishWords.length
const readingTime = Math.ceil(words / 275)
```

### 友链头像回退

以前 Hexo Next 友链头像挂了就是一片空白。当时想的是，如果头像挂了可以很快地知道谁出问题了，但是前不久才想到，完全可以做一个回退机制，美观而且还能看出谁的头像没了。

现在在 `links.astro` 里给每张图加了 `data-fallback`：

```astro
<img
  class="friend-avatar"
  src={friend.avatar}
  data-fallback="/friends/default-avatar.webp"
  是一张阿卡林
  onerror="this.src=this.dataset.fallback; this.onerror=null;"
/>

```

页面底部还有一个捕获阶段的全局 `error` 监听，防止个别图片漏网：

```js
document.addEventListener(
  'error',
  (event) => {
    const image = event.target
    if (!(image instanceof HTMLImageElement))
      return
    const fallback = image.dataset.fallback
    if (!fallback || image.src.endsWith(fallback))
      return
    image.src = fallback
  },
  true,
)
```

此外，欢迎加[友链](https://yuuu.org/links)。

---

## 老东西

有几样属于 Hexo 时代就在用、迁过来只是换了个挂载方式，单独开一节写配置截图也没意思，但完全不提又好像漏了什么，所以挤在一段里说完。

**代码高亮**换成了 `astro-expressive-code`（`astro.config.ts` 里集成，主题 `dracula`），文章里的代码块自带复制。**图片放大**还是 `medium-zoom`，在 `LayoutDefault.astro` 里对 `main .prose img` 做点击放大。

**RSS / Atom** 订阅地址也还在。但因为全文 slug 都换了，阅读器里很可能一口气冒出几十条新文章，其实只是我改了全部的链接，并不是我又狂更了三十篇。**在此向 RSS 订阅的朋友们说声抱歉**，烦请随手点掉重复项。

**Hexo 的 `<!-- more -->` 摘要**，说实在的，我现在还在想以后要不要删掉 `<!-- more -->`、改用手写 `description`。不过迁移的时候 AI 顺手兼容了，也就留着。

### Lighthouse 和 WebPageTest 测试

用了这么一个轻量的主题，而且还是作者在仓库里面展示了 Lighthouse 满分的主题，我当然也想弄到一个满分结果。

不过结果是，主要还是因为我自己加了不少东西（准确地说，是 AI 帮我加了不少），还有一些本可以继续精简的地方没有处理掉。

放上一些数据吧。

### 旧的数据

![旧博客 WebPageTest](https://cdn.yuuu.org/post-img/35/7.webp)
![旧博客 Lighthouse](https://cdn.yuuu.org/post-img/35/8.webp)
![旧博客 Page Performance](https://cdn.yuuu.org/post-img/35/9.webp)

### 新的数据

![新博客 Lighthouse](https://cdn.yuuu.org/post-img/35/10.webp)
![新博客 WebPageTest](https://cdn.yuuu.org/post-img/35/11.webp)
![新博客 Page Performance](https://cdn.yuuu.org/post-img/35/12.webp)

---

## 写在最后

直到 `redirects.json` 填完、搜索能用、404 能点开、友链头像挂了还能看见默认图，这场搬迁才算是满意。

也是爽爽烧完了那些 token，还自己赔了点钱进去。

重整博客的时候，才发现，有这么一个机会来更改整个博客确实是难得，我可以一次性修改很多以前留下来的错误。之前一直压抑着自己不要去动博客源码，这几天驱使 AI 不停的干活，也是过上了资本主义走狗的生活了。

目前就剩下一点点关于图片的 alt 的问题没有优化，也算是历史债务了，以后打算每隔几天更新一下，将所有的图片的 alt 都改正过来。

折腾暂告一段落。这间屋子又可以承载我的文字继续走好一阵了。

### 一些段子

写着写着，又想到了这一句话：

> "请问你的博客是那个给文本加上渐变配色代码块，用衬线字体 article 巨大阴影半透明背景加莫名其妙的地方抄来的 canvas 动效，打开会自动播放网易云音乐且加载一个 Live2D 小人挡住播放器控件，还有自定义光标加上点击特效且复制文本的时候会自作主张加上版权通告，切出网页以后 title 还会变成莫名其妙的文本内容的吗"

哈哈，事实上，我早在还没有建立这个博客之前，我就已经把这些东西都装上我最早的一个博客上了，不过都是很早之前的黑历史。~~现在已经全部都彻底抹除了，想找到也不可能了。~~

不过，既然说到这个了，那么我也提一下这些东西吧。

**自动播放网易云音乐**，这个是一个非常搞笑的功能了。能想出这个功能的人现在想想也是十分坏蛋了。随意操控别人的电脑播放音乐，这不就是病毒行为嘛？

**渐变配色代码块**，说实话我也没太搞清楚这个具体指的是什么。如果说的是仿 Mac 风格的三色圆点标题栏风格的代码块，那我用过；如果是那种类似用 lolcat 的配置输出一遍代码块……那我还真没见过，甚至开始怀疑世界上真的有人这么干过吗？

**衬线字体**这个我保留意见，而且事实上我现在的博客首页就在用衬线字体，在很多博客里面也看到用衬线字体做正文并不唐突。现在的屏幕分辨率普遍比以前高多了，衬线字体在高清屏上的渲染质量已经完全不是问题，反而多了一点质感。当然如果是在那种已经很花里胡哨的网页里面用宋体，那确实是另一回事了。

**巨大阴影半透明背景**，不知道说的是不是那种深色滤镜的背景图片？如果是，那这不就是 WordPress 的 [Sakurairo](https://github.com/mirai-mamori/Sakurairo) 和 Hexo 的 [Butterfly](https://github.com/jerryc127/hexo-theme-butterfly) 最喜欢的嘛。我承认我也被这个样式吸引过，老实说第一眼确实好看。但最后放弃的原因很实际：对背景图的要求太高了。要铺满整个屏幕，分辨率不够就模糊，分辨率够了图片体积就上去了，页面就慢了。压缩则难以把控流畅和清晰的度。没玩几个月就弃了。但是不影响我喜欢。

**Canvas 动效**，有名的项目我唯一想到的是 [canvas-nest.js](https://github.com/hustcc/canvas-nest.js) ，一个鼠标吸附连线的小项目。我自己没用过，因为当时喜欢用大背景图，两个叠在一起太乱。不过这个东西放久了有个问题：鼠标划来划去会吸附出一堆线，到后面整个屏幕乱成一锅粥。

**Live2D 小人**，以前超级爱用，最有名的是 [stevenjoezhang/live2d-widget](https://github.com/stevenjoezhang/live2d-widget) 。后来放弃的原因是加载依赖 jsdelivr.net，网络不稳定的时候直接加载失败，或者只加载一半的图片出来，不好看。

**自定义光标和点击特效**，笑不出来，我刚刚才把这两个换下来，我反省。

**复制加版权通告**，这个我感觉多此一举，特别是复制代码的时候也把通告一起复制进去，粘贴到编辑器里才发现后面跟着一大段版权声明，能不被逗笑的也是蛮有定力的了。

**切出网页 title 变奇怪文本**，也笑不出来，去年有一两个月用过，当时觉得特别有意思。但最后换掉的原因很蠢：当我切出去查资料的时候，回来居然找不到我的博客在哪个标签页了。行吧，已经影响到我了，只能删掉了。

现在想想还是感觉玩的很开心。其实这是一个很正常的阶段。刚学会做网页，每发现一个新插件都想往上加，Live2D 看板娘、canvas 粒子动效、鼠标点击，能加的全加上。一方面是单纯的新鲜感，另一方面也有点「能力证明」的心理在里面：加的东西越多，感觉自己会的越多。再加上那时候到处都是博客美化教程，自己学会了就又写一份博客美化教程，网上清一色都是教你「怎么加」，至于那种教你怎么减少的博客，大家相比之下也自然而然的忽略了吧？审美也就自然往「复杂 = 好看」这个方向跑偏了。
