跳转到内容

建站四年,从Hexo换到Astro

发布于: 2026-06-01
更新于: 2026-06-01
14 分钟
5,469 字
PV --
UV --

建站四年,从Hexo换到Astro 封面图

碎碎念

久等啦!最近一直在猛猛重构,刚把主页重构完成,现在又开始对博客下手了。

感谢鱼鱼赞助的 Token,让我终于不再有 Token 焦虑,也能更快地迭代和折腾项目。不得不说,AI 时代真的让很多人完成了一次“从想法到落地”的跨越,以前很多天马行空的东西,现在真的能快速做出来了。

这一年的发展速度,感觉比以前几年的变化还要快。最近也经常听到 Github 又宕机了,我甚至感觉某种程度上也是因为 AI 产出的代码实在太多了,提交次数变多、代码量暴涨,服务压力也越来越大,最后就容易出现各种问题。

以前想重构一个博客,基本只能停留在“想想”的阶段,因为成本真的太高了。尤其是上班之后,如果真要重构,就意味着要花大量时间去改架构、调样式、补功能,这样一来,反而没时间更新文章和做自己真正想做的事情。如果战线拉得太长,最后大概率就是兴趣被磨没,计划直接烂尾。

索性,后来出现了 AI

它确实大大缩短了我的重构时间,虽然我现在依旧天天熬夜,经常搞到十一二点,但至少很多以前“不敢开”的坑,现在终于能慢慢填上了。苦尽甘来!

框架选择

自 2021 年建站开始,我就一直使用的是 Hexo

Hexo 真的非常容易上手,对新手很友好,而且生态里有很多广泛流传的主题,比如 ButterflyFluid。这些主题我已经改到什么程度呢?基本不用看页面,光看代码我都知道哪个模块对应哪里。

但时间久了,也逐渐感觉到了 Hexo 的乏力。构建速度越来越慢,文章一多,构建时间几乎是成倍上涨,再加上性能方面也有一些先天限制,所以我慢慢萌生了换框架的想法。

其实早在 2022 年年底,我就已经开始琢磨换框架了。期间看了很多方案,比如 Hugo,号称速度最快的博客引擎。我也认真研究过,但始终没找到特别喜欢的主题,总感觉缺少一种“想改它”的欲望(笑)。

而且在我眼里,Hugo 有点属于“傻快傻快”的类型,生态相对独立,和现在很多主流前端框架的思路接轨没那么自然,所以最后还是放弃了。

后面又了解到了 NextJSNuxtJS,但体验下来感觉构建反而更重了。对于一个纯静态博客来说,我个人觉得没太大必要,毕竟我也不打算接后端,所以最后依旧选择放弃。

直到 2024 年年底,我接触到了 Astro

当时只是随手写了个小项目,结果发现这个框架意外地符合我的习惯。后来陆陆续续又用 Astro 写了个人主页、CDN 官网之类的项目,对它也越来越熟悉,积累了一些经验。

其实到了 2025 年年中,我就已经动过重写博客的念头了。但那时候的我,技术积累还不够扎实,说白了就是个大菜逼,根本没有能力完整重构一个博客,只能暂时搁置。

后来从 2025 年年底开始,我慢慢接触到了 vibe coding,也从 CV 工程师逐渐进化成了 Yes 工程师(笑)。随着对 AI 的使用越来越深入,我也越来越能感受到它带来的变化,于是正式开始了自己的重构之路。

前期进度其实非常慢,甚至可以说根本没进度。

那段时间看了很多博客,也尝试基于别人的项目进行修改,但最后基本都放弃了。直到 2026 年四月份,恰逢鱼鱼给了我一大堆 Token,于是我终于下定决心开始重构,每天蹬代码蹬得可开心了(笑)。

最终,整个博客前前后后大概花掉了接近一千刀,才终于把现在这个版本写完整。再次感谢鱼鱼的赞助!

目前整体框架已经逐渐稳定了。老博客后面有时间整理的话,我会慢慢开源;至于新博客,暂时还是决定闭源。

之前其实开源过一部分,结果后来网上出现了一堆“同名同姓”的站,甚至连名字都懒得改,那我也确实没什么办法(笑)。

当然,新博客现在还是有一些 Bug 的,欢迎大家前来抓虫!我也会尽我最快的速度去 vibe coding 把它们修掉。

毕竟,这大概也是我上班之后,唯一还能一直保持热情的娱乐方式啦~

设计

从迁移博客一开始,我就一直坚持一个原则:尽可能和原博客保持完全一致。

尤其是网站路径这一块,我是真的不太敢乱动。一直有个很现实的担心,就是如果路径结构一变,SEO 直接就可能出问题,收录掉一波还是小事,更麻烦的是后续各种链接、外链、历史索引全都会乱掉。对于博客这种偏内容沉淀型的站点来说,功能可以慢慢加,但稳定性和可访问性,对我来说是绝对不能动的底线。

一开始其实我是基于一个现成的博客项目做魔改的,想着先跑起来,再一点点往自己的逻辑上改。但改着改着就发现一个问题:很多开源项目看起来挺完整,但真正用在自己这套需求上时,总会有各种不顺手的地方,不是结构不对,就是扩展性不够,要么就是和我想要的内容组织方式对不上。

于是后面就变成了一个比较典型的“越改越重构”的过程。表面上还是在基于原来的项目改,但实际上很多核心逻辑已经一点点被替换掉了,包括路由、内容组织方式、构建流程等等。

现在回头看,虽然整个站点的视觉上可能还能看出一点老项目的影子,但底层结构其实已经基本换了一套新的体系,算是从“魔改”慢慢走到了“重构”的状态。

文章

首先最重要的,肯定还是文章这一块。

这部分基本可以说是整个博客里 SEO 权重最高的内容,其他功能就算出问题,文章链接这一块也绝对不能乱。毕竟老站已经积累了不少索引,如果这里一改动,搜索引擎收录基本就得重新来一遍,所以在迁移的时候,我的第一原则就是:必须尽量保持原有链接结构不变。

于是自然就绕不开 abbrlink 这一套逻辑。

Hexo 之前其实是有现成插件可以直接用的,但到了 Astro 这边,总感觉插件生态没那么“顺手”,很多东西不是不能用,而是用起来不够贴合自己的需求(笑),最后索性还是自己重新写了一套。

整体思路其实和 Hexo 差不多:

  • 如果检测到文章没有对应的永久链接字段,就自动生成
  • 支持从文章头部读取原有 permalink 信息
  • 自动生成新站对应的链接结构并保持稳定映射

这样做完之后,老文章迁移过来基本就能做到链接无感切换,SEO 也不至于直接崩掉。

文章绝对地址

除此之外,文章头部信息也需要一并迁移,比如发布时间、更新时间、cover、分类、标签以及各种元数据等等,这一块看起来不起眼,但实际做起来细节挺多的。

尤其是分类系统,Hexo 里不仅仅是一个分类字段本身,还带有路径映射机制。比如把“日常学习”映射到 /categories/learning/,这样可以避免中文路径在 URL 里变得又长又难看,也算是一个比较实用的小优化。

但标签这块就有点头疼了,因为数量实在太多,如果一个个做映射,工作量直接爆炸,所以最后我干脆偷了个懒,标签统一保留中文,反正能用就行。

目前文章页基本已经具备完整功能,包括阅读时间、字数统计、文章目录、代码高亮、封面展示、访客统计等内容,同时也接入了 busuanzi 用来做基础访问量统计。虽然很多人觉得这类统计意义不大,但我个人还是挺喜欢看数据变化的,多少有点“可视化成就感”。

外挂标签

这个基本算是整个迁移过程中最折磨人的一块之一。

之前甚至听说有群友在迁移的时候,宁愿直接改成写 HTML,都不愿意去适配这些外挂标签,可见这玩意到底有多麻烦。

但我这边属于典型的“头铁型选手”,还是决定硬着头皮往上冲。

一开始我最想做的是保持和 Hexo 完全兼容,比如这种写法:

{% link title,description,link %}

结果实际做下来才发现,这种语法在 Astro 里根本没有天然的插入点,渲染链路也完全不同。最后只能退一步,在 HTML 输出阶段做字符串替换。

问题也就随之而来:

内部 Markdown 渲染容易出问题、嵌套结构容易炸、边缘情况一多就很难兜住,还得不断做各种补丁式修复,整体体验非常割裂。

后来折腾了一圈之后,才找到一种相对可控的方案,就是用类似“提示块”的 DSL 写法,比如:

::link{title="TimePlus: 洪墨时光。由Heo维护的Time主题版本,基于Typecho" url="https://github.com/zhheo/TimePlus" desc="github.com@zhheo"}

说实话,这个写法我到现在也没完全习惯,相比之下还是 Hexo 那种老写法更直观一点(笑),但既然架构已经换了,也只能慢慢适应。

除了这种单行标签,还有类似按钮这样的内嵌写法:

:btn{title="点击跳转" url="/posts/1dfd1f41/"}

再往上还有更复杂的多行结构,为了完整迁移,我甚至连自己的 chatbox 都一起搬过来了:

:::chatbox{title="咕咕怪的小群"}

::chat[你好啊,我叫咕咕怪]{name="咕咕怪"}
::chat[你叫什么?我想请你办个事,@¥(*@¥@……&]{name="咕咕怪"}
::chat[多少钱?]{name="咕咕怪"}
::chat[咕咕]{name="咕咕怪"}
::chat[那可是我的手足兄弟……得加钱]{me}

这个效果在其他文章里应该还能看到:

聊天框组件

当时甚至还专门写过一篇文章来介绍这个 chatbox 的实现逻辑,这里就不展开了。

总之,这一块属于典型的“看起来不复杂,做起来全是坑”的部分,但好在最后还是一点点啃下来了。

接下来,就是另一个更大的坑了。

Pjax

虽然我现在实际用的是 Swup,但标题还是保留成了 Pjax,因为最开始接触这种局部加载方案的时候,用的就是 Pjax 这一套思路。说实话,这种东西我真的是又爱又恨,爱的地方很简单,就是体验确实爽,相比传统的整页刷新,网站整体的质感会瞬间提升一个档次,页面切换会显得特别丝滑,看起来更像一个完整的应用,而不是单纯“打开一个网页”。尤其是博客这种内容站,只要切换流畅一点,观感提升真的非常明显。

但恨的地方也同样明显,那就是坑真的太多了。以前我在 Hexo 上折腾 Pjax 的时候就是这样,只要哪个地方的 JS 突然不生效了,十有八九就是 Pjax 在背后偷偷搞事情。因为很多脚本默认只会在首次页面加载时执行一次,但局部刷新之后实际上页面已经被替换了,脚本却不会重新初始化,于是各种奇奇怪怪的问题就全来了。

现在换到 Astro 之后,其实也没逃过这个宿命(笑)。刚开始的时候,我尝试过 Astro 自带的无刷新方案,但研究了半天,总感觉它的逻辑有点绕,很多东西不知道该从哪里下手改,最后索性直接换成了自己稍微熟悉一点的 Swup。结果换完之后发现,本质上还是一样的东西,依旧是一步一个坑。

像什么侧边栏 JS、左下角音乐播放器、友链朋友圈、灯箱、评论区、页面动画,这些东西全都需要重新适配生命周期。很多功能看起来只是“显示一下”,但实际上背后都需要重新挂载、重新初始化,不然第一次加载正常,第二次切换页面直接原地去世。尤其是评论区和播放器这种依赖状态的东西,处理起来真的非常折磨。

最离谱的是,有些 Bug 根本不是稳定复现的,你刷新一次正常,切换两次页面突然炸了,再切回来又好了,属于那种最恶心人的类型。很多时候我花几个小时排查,最后发现只是某个事件监听没有销毁,或者初始化时机提前了一点点,纯纯折磨人。

一个星期之前加上的功能,到现在我才感觉自己勉强把 Bug 修得差不多了。当然,这里的“修好了”,很多时候其实只是“暂时看不见了”。毕竟我太了解自己的屎山代码了,它很有可能会在某个意想不到的地方突然发力,然后再给我整出点新花样。

不过也确实得亏现在有 AI,不然我感觉自己真能被这些生命周期问题搞崩溃。以前这种问题,很多时候光查资料都得查半天,现在至少还能有人陪我一起坐牢(不对,他好像不是人)。

懒加载

懒加载这部分相对来说还好,至少没有前面那些生命周期和局部刷新的坑那么离谱,目前全站图片基本都是直接使用浏览器原生的懒加载 API 去实现的,整体接入起来其实还算简单,也不需要额外引入太多复杂的库,所以这里就不展开细讲啦。

不过实际体验下来,我总感觉还是和以前差了点意思,尤其是在一些图片特别多的页面上会比较明显,比如友链页面。以前在 Hexo 的时候,同样数量的图片,基本可以做到从上到下一路顺滑加载,看起来非常跟手,虽然当时也没仔细研究过为什么,但体感上确实会更流畅一些。

现在迁移到新架构之后,一旦图片请求数量比较多,就容易被浏览器限制并发,请求会开始排队,导致部分图片出现“慢半拍”加载的情况。虽然不至于完全加载不出来,但总感觉没有以前那种一气呵成的感觉了,尤其是在网络环境一般的时候会更加明显一点。

当然,如果 CDN 状态比较好的话,其实问题倒也没那么严重,毕竟大部分静态资源现在都已经丢到 CDN 上了,缓存命中之后整体速度还是能快不少的。不过我还是感觉目前这套方案还有优化空间,比如资源预加载、缓存策略、请求优先级这些东西,后面估计还得继续慢慢研究。

尤其是 SW 这部分,我现在感觉其实还有很多能折腾的地方。如果后面能把缓存策略和资源调度再优化一下,说不定还能把现在这套加载体验继续往上拉一个档次。不过以我目前这个屎山状态来看,大概率又是一场持久战了。

SW

说到 SW,其实很多人对它偏见还挺大的,一提到 Service Worker,第一反应基本就是“缓存灾难制造机”,恨不得直接全站禁用,尤其是很多站长曾经都被它坑过,一旦缓存策略写不好,轻则用户资源不更新,重则直接缓存炸裂,页面和接口互相打架,最后连自己都不知道浏览器里到底缓存了些什么东西。

不过我个人其实不太排斥这个东西,我一直觉得技术本身没有绝对的好坏,更像是一把工具,重点还是看使用方式。SW 本质上其实就是一种浏览器侧的缓存机制,可以把网站的一部分资源提前缓存到本地,下次访问的时候直接从本地读取,而不是重新请求服务器,这样不仅能够减少请求数量,也能明显提升加载速度。

虽然“离线访问博客”这种场景,平时听起来确实有点奇怪,但我一直觉得,SW 更像是浏览器本地的一层 CDN。尤其是对于博客这种静态资源很多的网站来说,只要缓存策略设计合理,其实体验提升还是挺明显的,比如图片、脚本、字体这些资源,如果能够提前缓存下来,二次访问的速度会快很多,甚至切换页面的时候也会更加顺滑。

当然,它的问题也确实不少。最典型的就是缓存脏数据,很多时候你明明已经更新了文件,但用户浏览器里还是旧版本;还有更新机制也挺复杂,不是单纯替换文件就能解决的,经常需要考虑版本控制、缓存失效、资源清理这些问题。更别说如果处理不好,还有可能导致缓存无限堆积,最后浏览器存了一大堆没用的历史资源。最痛苦的还是调试,因为很多问题并不是代码本身有错,而是缓存状态不一致导致的,你看到的是新的,用户看到的是旧的,排查起来特别折磨人。

本站之前其实也是接入过 SW 的,只不过从 Hexo 迁移到 Astro 之后,要处理的东西一下子变得太多了,实在抽不出时间继续适配,所以目前只能先暂时搁置。现在优先做的事情,还是把整体功能和稳定性先整理好,后面再慢慢把缓存体系补回来。

不过为了防止以后重新上线 SW 的时候出现缓存冲突,我现在已经先做了一个专门清理旧缓存的组件,用来把之前遗留的 SW 缓存清掉。不然等后面重新启用的时候,浏览器里还残留着老版本资源,到时候新旧缓存一起发力,估计场面会相当精彩。

灯箱

最让我绷不住的是,某些博客模板居然连灯箱都不带。

我一开始甚至以为是我没找到配置项,翻了半天文档之后才发现,真没有。

没办法,最后还是只能使用传统艺能,自己手动接入 lightbox。其实这个东西单独接进来倒不算特别困难,真正麻烦的是和现在整套页面逻辑去适配,尤其是在 Swup 这种局部刷新的场景下,很多库默认都是按照“页面只初始化一次”去设计的,但我这里页面会反复切换,于是就会出现第一次正常、第二次失效、第三次直接抽风的情况。

包括图片节点重新挂载、事件重复绑定、页面切换后实例丢失这些问题,我都踩了个遍。有时候明明功能已经好了,结果切个页面回来突然又点不开了,最离谱的时候甚至还能同时弹出两个灯箱,给我自己都看乐了。

折腾了半天之后,目前总算算是稳定跑起来了,测试下来也暂时没发现什么特别严重的问题。不过我对自己的屎山代码还是很有自知之明的,现在没出问题,不代表以后不会出问题,说不定哪天某个神秘条件一触发,它就开始给我整活了,到时候估计又得抱着控制台熬夜排查,属于是博客写着写着,最后全在和 Bug 对线。

总结

实际上,这篇文章本来只是想简单记录一下本站的“新生”,结果写着写着就莫名其妙变长了。

说实话,我一直都觉得自己其实没写出什么特别“厉害”的代码,所以很多时候也不知道该怎么去总结这些东西。回头看下来,大部分内容其实都挺朴素的,就是一边踩坑、一边修 Bug、一边重构,然后硬生生把一个站点一点点磨出来。

很多时候也没有什么高深的设计,更多就是在现实约束里不断妥协、不断调整,最后找到一个还能跑、也还能接受的方案。

刚好今天也是六月一日,那就顺便祝各位大宝宝们儿童节快乐了。

另外今天 天涯社区 也算是正式回归了。以前一直在网上听说这是个很传奇的论坛,这次也算是亲眼见证了一下“回归现场”,多少有点时代感。不过目前看起来站点状态还不太稳定,我这边访问也经常超时,希望后面能慢慢稳定下来吧。

每日一图

图片来自哲风壁纸

护眼小房子