写在迁移之后:从 Hexo 到 Astro
Table of Contents
这三天,一直在用 AI 帮我迁移整个博客。 迁移的时候,看着 AI 在干活,我就在旁边心神放空地看着它干活。 回想起前几年,我还在想,如果日后网站添加了很多的东西,那么更改起来岂不是要花费好久好久的时间才能完全迁移到另外一个地方,这可能要花费不少人力。 结果这几天,我看着 AI 自己一点一点地把数据和很多自定义的东西都从 Hexo 搬到 Astro,有现成的就直接引入、安装、运行;没有的,就自己写个大差不差的版本。看到这里总感觉有点感叹——想不到时代发展这么快。一想到十年前我的电脑还只有 4G 内存,而家里二十年前的电子东西到如今只剩一个 DVD 机器还能运作。现在我却免费使用着由一个个数据中心支撑起来的大语言模型。
好了,先不说这么多了。下面说说这次博客更改之后的情况。
删除的
hexo-abbrlink
首先是一切的万恶之源 hexo-abbrlink!
我两天前写了一篇博文说明这个问题(见 关于 abbrlink 的一些见解)。对于这个插件,我只能说:「快拿掉!」
之前 AI 专门写一个 Astro 的路由来应付这些,在新的框架里居然还要做一条能让旧时代活下去的船嘛,无法想象。明明 Hexo 早就能自己写 permalink,居然还要用这个;之前的我真是没仔细钻研 Hexo 就开始部署网站,这是我的失误。
于我而言,我都花时间来换框架了,也不在乎这点破坏性的更新了:弄了一个 redirects.json,直接把旧链接全部做成 301 重定向,并且日后全部都使用 slug,自己写。
实现上并不复杂。根目录维护一份映射表,再在 astro.config.ts 里交给 Astro 的 redirects 选项:
{ "/post/16107.html": "/post/hello-world", "/post/37582.html": "/post/new-laptop", "/post/10005.html": "/post/about-abbrlink"}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,总体积还要更大。之前一直都对自己的网站的速度非常自信,因为我自己在 Cloudflare 里面开了一堆优化选项,Cloudflare 大善人能帮我解决一堆问题,但是因为没用小黄云,所以什么都没生效。
结果现在看来,简直就是在海底光缆里面运输💩,而且是臭不可闻的那种。

你可能会想:第三方 JS 可能有点难优化,但是为什么不先试试字体子集化、unicode-range 按需加载、动态子集化服务、格式优化成 woff2、上传国内 CDN 之类的操作来压缩字体?
其实我此前一直用 CSS 的 unicode-range 规则。而全站使用霞鹜文楷,首页就要下载 32 个子集化的字体文件,共 1 MB 的字体:

对于中文字体分包的情况,对 LXGW WenKai / 霞鹜文楷 来说,光是 CSS 就有 226 KB。
# cdn 里面的 result.css 大小$ ls -lh *.css-rw-r--r-- 1 li 197121 226K May 18 10:37 result.css分包之后文件夹里有 310 个 woff2(除去四个 css、演示页等,顶层文件):
$ find . -maxdepth 1 -type f | wc -l314也就是说,你要先下载一个 226 KB 的 CSS(可能更小,因为有 Brotli)。然后还要拉一群总大小大于 1 MB 的 32 个字体,然后才能开始渲染文本嘛,天哪。
综上所述,用这个实在是明显拖慢了速度。
所以我决定还是简单地来,在 src/.config/user.ts 里只保留系统字体栈:
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,Astro 没有同款;真要搓也能对接 Bangumi API 来实现一个差不多的,但我平时看番都懒得上 Bangumi 点一下,B 站版权又日益收紧,新番全部变成个人上传,开启了大盗版时代。也正因如此,我已经没法使用 B 站的番剧功能来看番了,那么还是直接舍弃掉吧。 我个人认为大家来博客大概还是看文章的;要看也应该对千字影评或读书心得更感兴趣,真有什么有意思的书本,我又读过,如果我有时间,我会直接写出来的。 这三个页面说白了是两年前想把博客做成「个人主页」时留下的,现在看来有点像是给别人展示用的,而不是真正为了记录什么。既然平时也没在维护,迁移的时候自然就顺手删掉了。
总之,只能说很有个性,但是除了摆出来让大家看一次,也没有别的益处了。
花里胡哨的装饰
鼠标样式
这个黑点当时花了好一些心思弄出来的。鼠标一会不动就会自动隐藏,滚动的时候也不显示,本意是让大家专注阅读。
然而据多人反馈(很多个时期的我),经常找不到鼠标。
而且我这半年看别人的博客就算有这个设计,也是不会隐藏的,而且更何况真要认真阅读的话,大家都懂得把鼠标移动到什么地方能方便阅读。
点击特效
优点:很酷。
但实测处理器 G1620 在多次点击的过程中不堪重负。
为了一些赛博苦行僧的阅读体验,也是为了博客简洁,还是删了吧。
地图预览
我之前的网站右上角有一个地图预览,添加的原因是:很酷。
不过现在把它关掉了,因为这个网站图在很多小屏幕电脑上会很占位置,以至于影响到了正文阅读。

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

新增的
404
想不到主题里居然没有像样的 404——不过考虑到我之前 Hexo-next 也没有,就让 AI 做了一个。
其实我自认为 404 还是有必要有的,对于 Vercel,一旦跳到 404,只能手动返回上一页,有些不便。
点击这里去看看 404。
src/pages/404.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。
$ ls -lh search.xml-rw-r--r-- 1 li 197121 229K May 20 12:32 search.xml构建索引:src/pages/search.xml.ts 在构建时把所有文章标题、URL、正文打进 XML:
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 用内联脚本拉取并过滤:
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。在 LayoutDefault.astro 里全局初始化,并配合 Swup 在切页后重新执行:
import pangu from 'pangu'
const pg = pangu as anypg.spacingPage()
document.addEventListener('swup:page:view', () => { initZoom() pg.spacingPage()})阅读进度条
这个功能对我来说算加分项,不是刚需。
ReadingProgress.astro 挂在默认布局顶部,监听 main 或 document 的滚动,用一条 fixed 的细线表示进度:
<div id="reading-progress" class="fixed top-0 left-0 h-0.5 bg-primary z-999 ..." style="width:0%"></div>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:
这个功能我仍保留意见,日后如果感觉太臃肿了,那就只保留在关于页面放一个就行了。
<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:
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 再展示:
const chineseChars = textOnPage.match(/[\u4E00-\u9FA5]/g) || []const englishWords = textOnPage.replace(/[\u4E00-\u9FA5]/g, ' ').match(/\b\w+\b/g) || []const words = chineseChars.length + englishWords.lengthconst readingTime = Math.ceil(words / 275)友链头像回退
以前 Hexo Next 友链头像挂了就是一片空白。当时想的是,如果头像挂了可以很快地知道谁出问题了,但是前不久才想到,完全可以做一个回退机制,美观而且还能看出谁的头像没了。
现在在 links.astro 里给每张图加了 data-fallback:
<img class="friend-avatar" src={friend.avatar} data-fallback="/friends/default-avatar.webp" // 是一张阿卡林 onerror="this.src=this.dataset.fallback; this.onerror=null;"/>页面底部还有一个捕获阶段的全局 error 监听,防止个别图片漏网:
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)此外,欢迎加友链。
老东西
有几样属于 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 帮我加了不少),还有一些本可以继续精简的地方没有处理掉。
放上一些数据吧。
旧的数据

新的数据

写在最后
直到 redirects.json 填完、搜索能用、404 能点开、友链头像挂了还能看见默认图,这场搬迁才算是满意。
也是爽爽烧完了那些 token,还自己赔了点钱进去。
重整博客的时候,才发现,有这么一个机会来更改整个博客确实是难得,我可以一次性修改很多以前留下来的错误。
目前就剩下一点点关于图片的 alt 的问题没有优化,也算是历史债务了,以后打算每隔几天更新一下,将所有的图片的 alt 都改正过来。
折腾暂告一段落。这间屋子又可以承载我的文字继续走好一阵了。
评论
评论加载中...