第四次博客重构记录

故事时间

作为时下流行的元框架概念的重要践行者与推广者,Next.js 几乎成为现代 React 开发的代名词;
凭借 utility-first、以原子类“规避原生 CSS 复杂性”的设计理念,TailwindCSS 在社区中风头正劲。
各类教程视频与文章早已把两者组合吹捧为现代前端最常用的技术栈。
而你是一名新手前端,两者的大名你早有耳闻,正打算跟随 Crash Course 快速入门。
做什么呢?你回忆自己有限的知识与过往项目,也许是——再整修一次博客?
很抱歉,你即将踏入一个名叫 overengineering 的泥潭。
那个你,就是我。

是的,我又在折腾自己的博客,第四次。

触底

第三次博客重构记录中,我用轻松的语气写下遇到的困难以及解决方法。但现在,我只想说我受够了,我搞砸了,I f*ckd up.

能力越大,配置越多

我的博客文章其实格式稳定、鲜少交互,Next.js 强调的扩展性在这里并非优势,反而成了额外的配置成本。它确实很强,但强不意味着任何场景都适用。自己动手搭积木,麻烦接踵而至:

Contentlayer 在其赞助方被 Netlify 收购后失去资金支持,开发基本停滞。我不得不把注意力从内容本身转向这个管理内容的工具。

Shiki 在 Next.js 中并非开箱即用,将其集成到博客项目中增加了我额外的心智负担。

按理说,React Markdown 渲染内容已经足够。但它和我亲手引入的 Shiki 起了冲突。同步异步的细节,迫使我钻研抽象语法树、学写 remark 插件。写文章的初衷演变成了构建渲染管线。

速成的样式糖,慢性的副作用

Tailwind 的原子类提供了极为强烈的即时反馈,却让我逐渐沉迷于做更多的效果,而非写更多的内容。我投入时间写入场动画、主题切换、链接预览卡片、可变字号的统计信息、版权声明…却忘记了博客的根本作用:表达思想。

@tailwindcss/typography 确实让排版快速成型,然而一旦我尝试微调细节,就撞上它抽象层的边界—— tailwind.config.js 中层层嵌套的配置与扩展选项,令人望而生畏。

当我改了一个页面的 className="mx-4 flex flex-col gap-8 pt-6",还要在八个地方重复相同的修改。所谓高效,最终却转化成了维护成本。

如此种种,都与博客的理念逐渐背离。回看整个项目,我才意识到,当工具开始主导决策,内容已然让位于工具。

转折

内容优先

初次接触 Next.js 时,我曾留意过 Astro,但将其视为新兴的实验性产品,并未深入了解。直到我对现有博客产生了不满,并与 ChatGPT 讨论技术选型,再次听到 Astro 的名字,才重新唤起了兴趣。阅读官方文档后,我发现 Astro 内容驱动网站的设计原则,真正契合了以内容展示为主的网站(比如博客!)的需求。

  • 默认输出纯静态 HTML, 零 JS, 仅在明确需要交互时按需引入客户端脚本;
  • 内容层 API,高效定义和加载内容,并提供类型安全的约束;
  • 内置基于 Shiki 的代码高亮,无需额外配置;
  • 开箱即用的 Markdown 处理流程,直接面向内容管理。

对以内容为中心的博客而言,这些特性意味着我不必深入学习和维护庞杂的 React 生态系统,而是将注意力集中在写作与内容组织本身。

JBOC

借用 JBOD (Just a Bunch Of Disks) 的命名方式,我杜撰了一个自嘲式的名词: JBOC (Just a Bunch Of CSS) ,即放弃追随潮流,切换到“复古”思路,直接使用最基本的、语义化的 CSS。博客场景下的样式需求并不复杂,不过是排版、配色、间距与响应式布局。原生 CSS 本就有模块和继承机制,借助合理的命名和现代特性完全可以胜任,引入复杂的工具反而是多余。

在我的实践中,JBOC 具体表现为基于语义化类名和模块化的 CSS 文件组织。我将样式拆分为全局、组件和文章特有样式。base.css 定义主题变量、字体栈、媒体查询等。components.css 针对导航和文章列表等模块化组件,而 article.css 则专注于文章页的目录和标签样式。这种结构化的拆分让我能够清晰地维护样式、实现主题切换(这次没有切换按钮了)和响应式适配,兼顾简洁与灵活性。

化繁为简

再次考虑页面设计,我决心削减非必要的功能与装饰:加载动画、Logo 动画、文章题图、链接预览、版权声明、独立归档页,统统去除。新的博客仅保留文章列表、正文页与标签页三类视图,采用统一布局,并针对移动端适配。

接下来是必要的交互。我最终只引入了移动端目录按钮及滚动监听 ScrollSpy, 以体现清晰的文章结构和满足基本导航需求,全部通过 <script> 实现。与其在复杂动画或次要交互上投入开发成本,不如将精力集中于排版质量和阅读体验。相信这种取舍能够带来更清晰的信息层级、更低的认知负荷,以及对我而言更明确的维护边界。

权衡

传统 SSG

回顾初代博客,我也曾用过 Hexo 这样的传统静态网站生成器 (SSG)。Hexo、Hugo、Jekyll 等 SSG,通常建立在某种模板引擎之上,其语法需要单独学习。尽管这些模板语法个个追求简洁,但从个人偏好而言,我始终很排斥它们。不论是 Swig、Jinja2 还是 Nunjucks,其逻辑总让我觉得冗余:既不像 JS 那样完整,又与 HTML 融合得很突兀。这些语法确实能够完成逻辑控制和内容渲染,但也意味着你在修改任何页面的内容前,必须额外记忆一套与 HTML 相似却又不同的规则。不想深究细节的我面对这些“似是而非的 HTML”常常感到挫败。

某种程度上,我想把模板语言称为 JSX 的雏形。模板语言在 HTML 中埋入少量自定义逻辑,JSX 则升级为用 JS 表达完整的界面。在内容型网站里,JSX 这种过度灵活性并不必要。既然我已经决定让 JSX 退出舞台,自然也没有理由重新拥抱另一种模板语法体系。

Astro 恰好作为中间形态出现。.astro 文件几乎可以被视作 HTML 的超集,避免了新一轮的学习成本。在我的博客场景中,这种“尽量接近 HTML”的选择比传统模板语言更加亲切,也比 JSX 更符合“内容优先”的原则。

CSS-in-JS

顾名思义,这种技术适用于交互复杂、状态繁多的大型应用(SaaS 后台、设计工具、可视化面板)。它将样式与组件深度绑定,实现样式随状态改变而精细、动态地变化。与 Tailwind 相比,CSS-in-JS 在与内容型网站的契合度上更差,甚至可以说完全错位。样式注入、额外的 JS 解析开销,全是不必要的复杂度。非但无法利用它的核心优势,反而承受了它的所有缺点。

SPA vs MPA

在 Next.js 的炮轰下,我一度将 SPA (Single Page Application) 视为现代网站的版本答案。得益于客户端路由和状态保持,SPA 在页面切换时几乎没有加载中断。这种无缝切换体验不仅像原生应用般丝滑,还有着快速的首屏加载,让我沉迷其中,确信 MPA (Multi Page Application) 是过时的产物,只有落后的网站才会采用这种方案。

现在我明白了,我错误地将一种技术的优势,等同于它在所有场景下的普适性。Next.js 提供的解决方案本质上是一个 SSG + SPA 的混合架构。它是在构建时预生成静态 HTML (SSG),才给了我一种快速首屏加载的感觉。但随后,它会向浏览器发送大量的 JS bundle,以便在客户端进行水合 (hydration) ——静态的 HTML 元素被 React 接管,变成一个完整的 SPA。

问题的核心在于,我为一项并不需要的、应用级别的能力支付了昂贵的性能代价。这些被下载和执行的 JS 文件,仅仅是为了实现客户端路由和支持潜在的交互组件,而我的博客 99% 的交互只是最简单的点击链接和滚动阅读。浏览器本就完美支持这些功能,无需任何框架介入。

MPA 并不是落后。在合适的场景中,它更高效、更稳健、更符合 Web “原有的样子”。没有虚拟 DOM 的开销,没有 hydration 的延迟,没有复杂的状态管理,一次导航就是一个全新的文档请求。照应了博客的核心价值:快速、清晰、稳定地呈现静态内容,而非向访客炫技。

尾声

当技术选择变成身份认同

前端圈子似乎弥漫着一种奇怪的部落主义 (tribalism)。开发者们往往不去理性评估工具与场景的匹配度,而是急于将自己归入泾渭分明的技术阵营。本是技术层面的讨论却时常滑向情绪化的对立。围绕各种标签的论战周而复始,却鲜少触及问题的核心:这项技术究竟在什么场景下能最优地解决什么问题?

Fact: 前端没有银弹。 每一场社区的热烈追捧,都只是在权衡之下,为解决特定问题提供的特定方案。它们生而不同,本无高下之分,唯有适用与否。我这次选择 Astro,用克制的方式实现博客,并非是在加入另一个新兴阵营,而是一次基于自身真实需求的评估后做出的选择,无关乎跟上队,只在意办对事。

选择框架或工具应当从需求出发,而非潮流或阵营。不能用站队代替思考,不能让情绪掩盖理性。

参考与致谢


随笔