← 全部文章

为什么我用 Astro 重写了这个博客

上一版是手写的 HTML,改一次导航要动七个文件。换 Astro 不是因为新,而是因为它把「内容」和「页面」分开,同时不往浏览器里塞运行时。

一只特立独行的熊猫 · · 约 2 分钟读完 · · #astro #web #design

这个博客的第一版是我用编辑器一个字一个字敲出来的静态 HTML。七个页面,每个页面里都有一份完整的导航和页脚。加一个栏目要改七个文件,改错一个就会出现两套导航。

我忍了大概半年。

排除法的结果

先说没选什么。

Next.js。它的静态导出能力没问题,但一个纯内容站带上 React 运行时,为了三处交互付出一百多 KB 的代价。这笔账不划算。

Hugo。构建速度没得说,问题在于模板语法。当我想在页脚做一点非标准排版时,得去查它的模板函数手册。改版一次查一次,非常消耗耐心。

纯手写 + 构建脚本。我试过用 Python 拼字符串生成 HTML。三天后我写出了自己的、只支持我需要的三个功能的模板引擎——这是重造轮子的经典开局,而且我已经开始考虑给它加缓存了。

Astro 解决的具体问题

Astro 的核心承诺是:默认输出零 JavaScript。组件在构建时渲染成 HTML,只有被显式标记为交互的部分才会带着脚本发到浏览器。

对内容站来说,这正好命中要害。我的页面里唯一需要 JavaScript 的地方是明暗主题开关,它不需要感知任何组件状态,只需要往 <html> 上写一个属性:

const root = document.documentElement;
const next = root.dataset.theme === 'dark' ? 'light' : 'dark';
root.dataset.theme = next;
localStorage.setItem('theme', next);

十二行,放在 <script is:inline> 里,不参与打包。翻遍整站,客户端脚本就这么多。

布局即组件

改版后,导航只有一份定义:

---
const nav = [
  { href: '/blog/', label: '文章' },
  { href: '/tags/', label: '标签' },
  { href: '/about/', label: '关于' },
];
---
<nav>
  {nav.map((item) => <a href={item.href}>{item.label}</a>)}
</nav>

要加栏目,改数组就行。这是组件化的基本收益,但它解决的是我上一版最痛的地方。

代价

说清楚不划算的部分。

第一,构建是必须的一步。改一个字要等构建完成才能看到结果——开发服务器启动后是毫秒级热更新,但部署前那次完整构建,在几百篇文章的规模下会从几秒涨到几十秒。纯手写 HTML 没有这个问题,因为它根本没有构建。

第二,我得知道 Astro 的边界在哪。什么时候用 .astro 组件,什么时候用 Markdown,什么时候写集成——这些决策有学习成本。好在它的心智模型足够小,我大概花了一个周末就摸清了。

第三,也是最重要的:换框架解决的是重复劳动,不是写作本身。这个博客最难的部分从来不是导航有几份,而是把「为什么这么做」写清楚。

回到最初的问题

如果要给一句话的结论:当你的站点里「内容」远多于「交互」时,Astro 的默认值是对的;如果你的页面里有一半是实时状态,它就不是。

我这次重写减少了文件数量,但没有增加文章数量。工具选型只负责让我不因为维护而放弃写作,它不负责让我有话可说。

全文完