新版BLOG小站来了

换域名、换框架、换部署方式,我的个人小站又重写了一遍。这是第五次。
如果是别人跟我说“我把博客又重写了”,我大概会笑一下,心想又是哪个闲不住的人。轮到自己,才发觉这件事其实没什么好笑的——它更像是某种周期性的发作:过一两年,就会对正在用的那个站产生说不清的不满,然后忍不住推倒重来。siky.top 这个域名跟了我很多年,中间换过好几任寄居在它上面的程序。每一次重写完,我都会在浏览器的地址栏里敲下这几个字母,看着新页面慢慢加载出来,心里有一种很短暂的满足,像是把一件旧家具重新刷了漆。但这种满足维持不了多久,过阵子就会被新的不满意盖过去。这一次,我想认真记一下它是怎么变成现在这个样子的,也顺便说说,为什么这一次和前几次不太一样。
以前那些版本
前几版的具体样子,说实话我已经记不太清了。能想起来的都是些零碎的画面:某个深夜在编辑器里调 CSS,某次因为部署没成功对着报错发呆,某个版本上线后兴冲冲发朋友圈,然后过几天就再也不想打开它。还有那种改了一个小样式,本地看着挺好,推到线上却整个错位的沮丧;以及某次服务器不知怎么挂了,隔了两天才发现,期间谁来看过、看到的是什么,我完全不知道。
这些版本之间没什么传承,每一次都是推倒重来。换一个框架,换一套风格,换一种组织文章的方式。做完的时候都觉得“这回稳了”,过一阵又开始嫌弃。现在回头看,这种反复未必是技术问题,更像是我一直没想清楚自己到底要一个什么样的站。于是每隔一阵,就把这份没想清楚,换一身新的衣服再穿一遍。换个框架,好像就换了一种可能性,好像这一次就能写出更多文章似的——其实文章还是那几篇,只是外壳又新了一层。
唯一记得比较清楚的是第四版,因为它是最近的一次,也是被我推翻的那一个。
第四版:一套自以为精巧的机器
第四版我下了点功夫。前端用 React 写,后端用 Node.js,服务器是腾讯云的一台机器,没有数据库。
听上去有点重,但我当时挺得意。整套流程是这样的:我在网页上的编辑器里写文章,写完点发布,后端的 Node 服务会把这篇文档落成一个 md 文件,然后 push 到 GitHub 上的 blog_repo 仓库里。服务器每次重启的时候,会从这个仓库把所有的 md 文件拉下来,扫一遍,生成一个 list.json。当前端请求文章列表的时候,Node 就去解析这个 list.json,把结果返回给前端,前端再渲染成列表。
没有数据库,内容都存在 Git 仓库里,听着挺干净的。我当时觉得这套设计很巧妙:写文章不用碰命令行,发布就是一次 push,历史版本有 Git 兜着,服务器重启还能自动重建索引。一切都显得很有条理。我还记得自己跟朋友描述这套流程时那种小小的得意,好像做了什么别人没做过的事。其实别人早做过,而且做得更简单,只是当时的我看不见。
但用着用着,别扭的地方就慢慢露出来了。
服务器是要续费的。一年一年地续,有时候会想这一台机器到底为我做了多少事,值不值这点钱。写完文章点发布之后,得等 Node 把 md push 上去,有时候网络不好,这一步会卡住,我还得到服务器上去看日志。更麻烦的是,新文章要等服务器重启才能出现在列表里——而我并不总是有空去重启它。于是我养成了一个奇怪的习惯:写完一篇文章,先放着,等哪天顺路重启服务了,它才会真的“发布”出去。发布这件事,从一件顺手的事,变成了一件需要专门安排的事。
一个人维护一套前后端,说起来都是小活,但加在一起就是一份持续的心智负担。哪天 React 升级了,依赖要跟着升;哪天 Node 有个安全补丁,得登上去更新;哪天服务器厂商发邮件说实例要迁移,又得折腾一晚上。这些东西每一件都不大,但它们会一直挂在脑子里,像没关紧的水龙头,一滴一滴地漏走注意力。
最要命的是,这套机器跑起来之后,我反而更不想写文章了。因为每写一篇,都要先在心里过一遍“发布会不会出问题”。一个本该让我放松的地方,变成了另一个需要照看的工作。我后来翻 blog_repo 里的文章时间戳,能很清楚地看到这一点:某一阵子密集地写了几篇,然后就是长长的空白,空白里不是没东西可写,而是懒得去走那一套流程。机器越精巧,写的门槛反而越高,这是我当初设计的时候完全没想到的。
想通了一件事
某天我又开始对第四版不满意,准备重写的时候,停下来想了一下:我到底在折腾什么。
博客是写给人看的。准确说,首先是写给我自己看的——一个能安安静静放文字的地方,写完能稳定地打开,别人来了能顺畅地读,这就够了。它不需要一套在线编辑器,不需要一台常驻的服务器,不需要一个会自动 push 的后端。这些我之前当成“功能”的东西,其实都是我自己给自己加的负担。
我写文章的场景很简单:在本地用 Markdown 写,写完提交。那么博客系统要做的,就是把一堆 md 文件渲染成网页,仅此而已。静态站点就能做到,根本不用后端。
想通这一层之后,后面的事就顺了。该扔的扔,该轻的轻。这一次不再追求“功能完整”,而是追求“足够简单,简单到我愿意一直用它”。说来也怪,这个道理并不复杂,我却绕了好几年、好几个版本才肯承认。大概人对自己亲手做出来的东西总有种舍不得,明明知道它是负担,还是觉得“万一以后用得上呢”。直到负担把写的念头都压没了,才舍得放手。
在终端里找个搭子
这次重写,我不是一个人干的。准确说,是和一个叫 Claude Code 的程序一起干的。它跑在我的终端里,我打字跟它说要做的事,它读我的代码、改文件、跑构建,然后把结果告诉我。
我一开始没指望它能帮多少忙。这类工具用过几个,体验大多停在“能答问,但不能真上手”的阶段:问它一段代码怎么写,它写得挺好,可我得自己复制、自己粘、自己跑、自己改。用几次就嫌麻烦,又回到自己手写。但这次不太一样:它能直接看到我整个项目的目录,能读我的 README 和老代码,能动手改完再让我看。我说“把首页换成 Hub 风格”,它就去看我之前 siky.top 那版的设计,照着那个调性在新的框架里搭出来;我说“文章没有封面的时候自动生成一个”,它就真的去写了一段用 SVG 渲染封面的逻辑,连中文都能正常显示。我说“换个暗色主题,别闪白屏”,它就去改 head 里的脚本,把主题在页面渲染之前就定好。这些事如果我自己干,每一件都得查文档、试错、来回改,现在一句话过去,它先给我一版,我再在上面挑毛病。
怎么说呢。它当然不是完美的,经常改一处坏一处,我得盯着、纠正它。但有它在,那种“一个人对着空项目不知道从哪下手”的茫然少了很多。打个比方:以前是自己一个人搬砖,现在是旁边多了个帮手,虽然这个帮手偶尔会搬错地方,但至少你不是一个人在那儿闷头干。这种踏实感,比效率提升更让我受用。
我不是要把这事说得多神。它就是个工具,会犯错,会跑偏,需要我判断和拍板。但对于一个白天已经写了一天代码、晚上只想随便弄弄自己小站的人来说,旁边有个搭子陪着一起折腾,这件事本身就够了。
换一身轻的骨架
技术选型很快定了下来。
框架用 Astro。它是纯静态生成,构建的时候把所有页面提前渲染成 HTML,部署上去之后不需要任何后端在跑。这正好是我想要的:写完 md,构建一遍,推上去,完事。没有服务器要续费,没有 Node 进程要照看,没有“重启才能发布”的等待。
部署放在 Cloudflare Pages。把仓库连上,每次推送,它自动构建、自动发布,还带 CDN。我不用再登服务器,不用再管证书,不用再半夜爬起来重启什么。
最有意思的是文章内容的处理。第四版里,blog_repo 是被我的 Node 服务 push 的对象——我写完文章,服务把 md 推上去。这一次,我直接让博客项目用 git submodule 的方式,把 blog_repo 作为内容源挂进来。也就是说,那个老仓库从“被推送的目标”,变成了“内容的家”。博客代码和文章内容彻底分开:代码在一个仓库,文章在 blog_repo,两边各自更新,互不干扰。
写文章的流程变成了最朴素的那个:在 blog_repo 里新建一个 md 文件,写,提交,推送。Cloudflare 检测到推送,重新构建,几分钟之后新文章就上线了。没有中间商,没有等待重启,没有“等哪天顺路再发布”。
把第四版那套 Node 服务整个删掉的时候,我有一种说不出的轻松。不是因为它写得不好,而是因为我终于承认:对一个个人博客来说,那套东西是多余的。
它是怎么自动发出去的
写完一篇文章,我要做的只有一件事:在 blog_repo 里写 md,提交,推送。剩下的一串事,是两个 GitHub Action 和 Cloudflare 接力做完的。画出来大概是这个样子:
blog_repo 里写 / 改 md
│
│ git push (main)
▼
┌──────────────────────────────────────┐
│ ① blog_repo 的 GitHub Action │
│ trigger-blog-v5.yml │
│ on: push(main) │
│ → 用 PAT 调 blog-v5 的 │
│ repository_dispatch │
│ event_type: content_updated │
└──────────────────────────────────────┘
│
│ dispatch 信号
▼
┌──────────────────────────────────────┐
│ ② blog-v5 的 GitHub Action │
│ update-content.yml │
│ on: repository_dispatch │
│ → checkout(含 submodule) │
│ → git submodule update --remote │
│ → commit 新指针 │
│ → push 到 blog-v5 的 main │
└──────────────────────────────────────┘
│
│ push (main)
▼
┌──────────────────────────────────────┐
│ ③ Cloudflare Pages │
│ Git 集成连接 blog-v5 │
│ → pnpm run build → dist │
│ → 自动部署上线 │
└──────────────────────────────────────┘
│
▼
新文章上线 🎉
中间这一段,是后来补上的自动化。blog_repo 一旦有 push,它自己的 Action 会去“敲一下” blog-v5 的门;blog-v5 收到信号,就把 submodule 指针往前挪一格,指向 blog_repo 最新的提交,再把自己 push 上去;这一 push,Cloudflare 就开始构建部署。全程不用我登任何一台机器,也不用我手动去 blog-v5 里改指针。需要的前置准备,就是一个有 repo 权限的 Personal Access Token,分别存进两个仓库的 Secrets 里,让两边能互相认证。
说到底,这套东西替我做的,就是把第四版里那个“等哪天顺路重启服务再发布”的环节,彻底拆掉了。
让它长得像我自己
骨架搭好之后,剩下的是让它好看一点,或者说,让它长得像我自己想要的样子。
我没有去找什么现成的主题。siky.top 之前有一版 Hub 门户的设计,亮蓝色的底,等宽字体的终端味道,背景里有几何图形在慢慢飘。我挺喜欢那个调性,就让它照着这个方向做。
于是首页变成了一个有点像终端启动画面的东西:背景上菱形、圆、三角、方块在缓慢地漂浮,中间是 SIKY_HUB 几个大字,底下一言 API 轮播着从网上抓来的句子,点一下还有个 QR Clock 弹窗,页脚藏了个十六进制的彩蛋。这些东西没什么实际的“用处”,但它们让这个站有了一种气质——一种我看了会觉得“嗯,这是我的”的气质。
我偏爱这种终端风格,大概是因为写代码写久了,对等宽字体和命令行有种说不清的亲切。亮蓝色也不是什么精心挑出来的颜色,就是某天试了一下觉得顺眼,然后就留下来了。一个个人站点的好处就在这里:你不用为任何人的口味负责,喜欢什么就放什么,哪怕有点幼稚,哪怕别人觉得没必要。
阅读体验上也补了一些东西。文章页有浮动的目录,滚动到哪一段会自动高亮;顶上一条阅读进度条,能看见自己读了多少;有阅读时长估算,有代码块的复制按钮,有回到顶部。明暗双主题都做了,默认是暗色的蓝底,想换亮色一键切,而且切换的时候不会闪一下白屏。背景的几何动画在页面之间跳转时也不会断,会一直连续地放下去——这种小细节,用的时候不一定察觉,但没有的时候就会觉得哪里不对劲。
这些功能单拎出来都不起眼,加在一起,读文章的时候就能感觉到一种“被人照顾过”的顺。我希望来这个站读东西的人,能稍微舒服一点。
没那么顺的地方
也不是一切都顺。重写的过程中,坑踩了不少。
构建一开始是失败的。SPEC 里我都专门记了一笔“当前会失败”。具体是哪个依赖的版本对不上、哪个配置写错了,这里不展开了,反正就是那种典型的“别人能跑我跑不起来”的折磨。和 Claude Code 一行行对着报错抠,改一个错,出来下一个,改完下一个,才终于看到构建成功打出 dist 目录。那一刻谈不上多激动,更多的是松了口气——能跑就行,别的要求先不提。
还有个坑是文章的网址。Astro 会根据文件名自动生成网址,但它在生成的时候会把文件名小写化,空格变成连字符,一些特殊字符还会被剥掉。我的文章文件名是中文加各种符号,这一套规则下来,生成的网址和我想的不太一样,有的文章甚至差点对不上。摸清楚这套规则之后,心里才有底,知道哪些文件名能用、哪些会有问题。
最折腾的是文章封面。我希望每篇文章都有一张封面图,但不是每篇我都想手动配图。于是让它做了一个自动生成封面的功能:没有配图的文章,自动渲染一张带品牌标识的 SVG。麻烦出在中文上——SVG 里直接写中文,有些环境渲染不出来,得用 foreignObject 去套一层 HTML 才能正常显示中文。这个绕来绕去的小问题,前后调了好几次才搞定。零依赖、不引外部库,纯靠 SVG 自己渲染出能看的中文字,这件事最后是成了的,我挺满意。
这些坑,现在写下来轻描淡写,当时都是一个一个抠过去的。没有什么“那一刻我顿悟了”的戏码,就是反复试、反复改,直到它能用。有时候一个晚上就耗在一个小问题上,睡前还在想,第二天醒来第一件事是再跑一遍构建,看那行报错还在不在。这种较劲没什么光彩,甚至有点笨,但它就是做事的真实样子。
老朋友 blog_repo
写到这里,想单独说说 blog_repo 这个仓库。
它跟了我好几版了。第四版的时候,它是被我的 Node 服务推送的目标——我每写一篇文章,服务就把 md push 到它里面。它像一个被我喂数据的容器,重要,但被动。
第五版里,它变成了内容本身的家。博客项目通过 submodule 把它挂进来,所有文章都住在里面。我直接在它里面写 md、提交、推送,博客会自动读取。它从被动接收,变成了主动承载。
这个变化听着小,对我却有点意义。好几年攒下来的那些文章,那些在不同的版本之间搬来搬去的 md 文件,终于有了一个稳定的、不再会被推翻的归宿。框架可以再换,部署方式可以再变,但 blog_repo 这个仓库,我大概不会再动它了。它是这几版折腾里,唯一活下来并且会一直活下去的东西。
第五版,大概就这样
siky.top 现在跑的是第五版。亮蓝底,终端味,几何图形在背景里慢慢飘,文章安安静静地躺在 blog_repo 里,Cloudflare 帮我把它送到每一个打开浏览器的人面前。没有服务器,没有后端,没有要续费要重启的麻烦。
我不敢说这是最后一版。按照过去的规律,过一两年,我大概又会看它不顺眼,忍不住再动一遍。但这一次有一点不一样:这一版足够轻,轻到我几乎没有维护它的负担;它也足够简单,简单到我愿意一直往里写东西。而且这一次,我不是一个人搭出来的,旁边有个搭子陪着,虽然它只是个跑在终端里的程序。
也许下一次重写还会来,也许不会。但至少现在,这个站是我这么多年来,最接近“想要的样子”的一个。它不再是一台需要照看的机器,而是一个能安静待着的地方。我想写的时候就写,不想写的时候,它就自己在那儿,亮着蓝底,飘着几何图形,等下一次有话想说。
如果你正好打开了它,随便逛逛。要是读到哪篇觉得还凑合,那我这第五次折腾,就算没白费。