从 Halo 搬出来以后,我自己做了一个网站

从 Halo 博客迁移到自建静态网站的一次完整记录:为什么决定重写、怎样兼顾本地可视化管理与纯静态部署,以及 GitHub 和阿里云之间的自动发布链路。

今年二月写“我的第一篇博客”时,我还在用 Halo。当时折腾完 NAS、内网穿透和域名,终于把博客跑起来,心里想的还是:以后总算可以只管写东西,不用再碰网站本身了。

结果没过多久,我又开始折腾网站了。

Halo 本身当然是一个很成熟的项目,后台、主题、评论、附件这些东西都已经准备好了,安装完就能用。对于想快速搭一个博客的人来说,它比自己从头写省心得多。我当初选择它,也是因为受够了用编辑器维护 Hexo 的方式,希望有一个真正的后台。

问题是,用得越久,我越清楚自己需要的其实不只是一个博客。

我这台服务器的配置不算高,Halo 跑起来以后,Java 服务、数据库和其他配套服务加在一起,内存经常要占掉一点几个 G。这个数字放在大服务器上不算什么,但放在只托管个人内容的小机器上,就显得有些奢侈了。尤其是大多数时候网站只是在返回几篇文章和一些图片,却需要一直养着一套后台服务,我总觉得不太划算。

真正让我决定换掉它的,还是自定义这件事。

我开发了几款思源笔记插件,普通文章只能解决“写一篇介绍”的问题,解决不了“把一个项目长期放在网站里维护”的问题。比如主页插件有完整的系列教程,教程首页还要读取 GitHub 上的版本和 Release;读书笔记插件除了教程,我还想给它做一个完全独立的产品官网;VIP 权益、更新日志、项目微站也都应该有各自的样子。如果继续套在博客主题里,每加一种页面都要先想办法绕过原有布局,最后很容易变成在别人的框架里打补丁。

远程数据也是一样。我希望插件发布新版本后,教程首页的版本号、最低思源版本和更新记录能够自己刷新,而不是每次发布完插件,再去博客后台手动改一遍。这个需求并不复杂,但它已经不太像传统博客该负责的事情了。

想明白以后,我干脆重新开了一个项目。目标很朴素:线上只留下静态文件,本地保留一个好用的后台;文章、教程和独立网站放在同一套源码里,但每种内容可以有自己的页面。

前台最后选了 Astro。我比较喜欢 Astro 的一点,是它默认就愿意把页面老老实实地生成为 HTML。没有交互的地方不必带一整套前端运行时,真正需要复制按钮、搜索或图片预览时,再加一点 JavaScript。构建结束后,整个网站就是一个 dist 目录,服务器不需要 Node.js,也不需要数据库,Nginx 把文件送出去就行。这种感觉有点像重新回到了 Hexo,但这一次我没有把内容管理也一起丢掉。

本地内容后台用的是 Keystatic。文章、教程和网站设置看起来是在表单里编辑,保存后实际上仍然是仓库里的 Markdoc 和 YAML 文件。数据没有锁进数据库里,也没有藏在某个在线服务中。哪天我不想用 Keystatic 了,这些文件还是普通文本,换个编辑器照样能改。

本地 Keystatic 文章管理界面
文章在本地后台中管理,保存后仍然是仓库里的内容文件

不过 Keystatic 只能解决内容编辑,不能把我想做的所有事情都包进去。后面我又在旁边加了一个自己的本地管理控制台。开发站点有没有启动、上一次构建是否成功、附件有没有失去引用、远程数据要不要刷新,都可以在这里看。平时写完文章后,我可以直接做类型检查、内容校验和生产构建,不必每次都去记命令。

个人网站本地管理控制台
我给自己补的本地控制台,把预览、构建、校验和附件管理放在了一起

附件管理也是后来越写越多才补上的。刚开始我只想做一个上传按钮,后来发现图片改名会让正文链接失效,重复附件会越积越多,压缩以后还要确认原来的引用有没有跟着切换。现在这套附件库会扫描文章和页面里的引用,图片可以统一压缩,删除先进入回收区,替换路径时也会同步修改引用。它不是什么通用 CMS,只是把我自己会遇到的麻烦一点点收进来了。

教程比普通文章更麻烦。主页插件现在已经有二十多篇章节,还有父子层级、更新日志和 VIP 专页。如果只靠文件名和数字排序,改一次结构就够我头疼半天。所以控制台里又多了一个教程管理页面,可以直接看整棵章节树,调整父子关系和顺序。这个功能放在成熟博客里可能显得奇怪,对我的网站却刚好合适。

个人网站教程章节树管理界面
教程不是一串互不相关的文章,而是一棵可以单独维护的章节树

独立微站则是我最想要的那块自由。它们和主站共用域名、仓库与部署流程,但不必继承博客的页头、页脚和文章样式。一个项目只要把 HTML、CSS 和需要的静态资源放进自己的目录,就能拥有完全不同的视觉设计。主页插件官网、读书笔记插件官网已经是这样做出来的。以后如果还想做小工具、演示页或者新的项目站,也不必先修改整套博客主题。

这里说的“高度自定义”,并不是说现在这个网站已经多精致。它现在仍然很简陋,有些页面甚至还带着明显的开发痕迹。但我更在意的是,接下来想加什么,可以直接在源码里加;哪里不好用,就顺着自己的习惯改。缺什么补什么,不需要的功能就根本不放进来。

这和直接使用现成开源博客的区别,大概就在这里。成熟项目为了照顾更多用户,必然会提供很多配置、权限、主题和扩展能力,这些功能都很有价值,只是其中大部分我用不到。自己做的网站反过来,它不追求什么都能做,只负责把我真正会用到的东西做好。代码量可能会越来越多,但每一块为什么存在,我自己都知道。

线上发布这部分,我也不想再手工登录服务器上传文件。现在文章在本地写完并提交到 GitHub 后,main 分支的推送会触发 GitHub Actions。它会安装 Node 22 环境,刷新插件的 GitHub Release 和清单数据,运行测试与类型检查,然后用 Astro 构建静态页面,再由 Pagefind 生成全文搜索索引。

如果是主页插件或读书笔记插件发布新版本,也可以通过 repository_dispatch 通知网站仓库重新构建。除此之外,工作流每天还会检查一次远程数据;内容没有变化就跳过部署,真的有新版本才继续往下跑。这样教程首页看到的版本信息,不需要我再手工维护一份。

构建完成后的 dist 会被打包,通过 SSH 上传到阿里云服务器。服务器端不会直接覆盖正在访问的目录,而是为每次发布建立一个独立版本,再把 current 软链接切换到新版本。这个切换几乎是瞬间完成的;哪次构建有问题,也可以把链接指回前一个版本。目前服务器只保留最近五次发布,旧版本自动清理,既能回滚,也不会一直占空间。

Nginx 负责最后一段很单纯的工作:返回静态 HTML、图片和搜索资源。带哈希的前端资源可以长期缓存,HTML 不强缓存,图片和附件则按类型设置缓存时间。线上没有内容后台,没有常驻的 Node 服务,也没有数据库。和以前相比,服务器终于更像一个安静的文件柜,而不是为了几篇文章一直开着一套应用。

从本地写文章,到 Git 提交,再到 GitHub 构建和阿里云发布,现在整条链路已经跑通了。平时真正需要我做的,只剩下把内容写好,然后推到仓库。剩下的检查、打包、上传和目录切换,都交给自动化流程。

回头看,我并不是因为 Halo 不好才重新做网站。恰恰是因为先用过 Hexo,又用过 Halo,我才慢慢知道自己真正介意什么:既不想每次写文章都和命令行较劲,也不想让线上长期运行一套自己用不到大半功能的后台。

现在这套东西正好卡在两者中间。本地有可视化管理,线上又是纯静态;普通文章可以安静地阅读,教程能维护自己的章节结构,项目还可以长出完全独立的官网。它未必适合别人,但目前很适合我。

以后它肯定还会继续变。也许会增加新的内容组件,也许附件库还会再改,也许哪天我又觉得某个页面看不顺眼,直接把它重写一遍。自己维护源码最大的好处,大概就是不用提前把所有需求都想清楚。先让它能用,再在真正遇到问题的时候补上那一块。

这个网站本身,就是我目前写得最长的一篇“长期笔记”。