LegspCpd Blog

我把博客的样式系统推倒重写了一次

一次从"能跑就行"到"讲清楚为什么"的返工。记录了变量缺失、路由冲突、样式失效这些看起来互不相关却同源的问题。

技术

· 约 1 分钟

这篇不是教程,是一份返工记录。

起因

某天我发现改一行样式,全站十几页没反应。打开控制台一看,background-color 那里赫然躺着一个空值:

background-color: var(--su-card);
/*                        ^^^^^^^^^ 没定义 */

于是全站排查 var(--su-*) 的引用量——190 多处,而定义它的地方是 0 处。

为什么这么久都没发现

因为大部分页面根本没走这套体系。

项目里同时存在两套布局:一套顶栏的,一套侧栏的。顶栏那套自带完整令牌,风生水起;侧栏那套的所有样式都指向一批从未定义的变量。

于是就出现一个很微妙的状态:

  • 你打开首页,一切正常
  • 你打开某篇文章,一切正常
  • 你打开侧栏布局的某个页面,卡片没了边框,背景透出底色,文字层级全部塌掉

而这些页面往往不是核心页面,不点进去根本看不到。

CSS 变量未定义时的行为

这是最值得记住的一点:

color: var(--su-foreground);

当 --su-foreground 未定义时,整条声明作废,不是“回退到继承”,而是直接被丢弃。所以 color 会走继承,而 background-color 会走初始值 transparent。

结果就是:看起来“有点淡”,而不是“明显坏掉”。这种半死不活的状态,比彻底崩掉更难查。

第二个坑:两个页面抢同一个路由

修完样式,构建通过了——然后日志里躺着一行警告:

[build] Could not render `` from route `/[...page]`
       as it conflicts with higher priority route `/`.

原来首页被两个文件同时渲染:

  • index.astro 渲染首页
  • [...page].astro 的第 1 页也是首页

而 [...page] 是 catch-all,优先级更低。谁优先不代表谁生效,只代表谁被静默丢弃——Astro 不会报错,只会警告。

更坑的是过滤条件:

// 有 bug:首页的 page 参数是空字符串,不是 "1"
return pages.filter((p) => p.params.page !== "1");

在 trailingSlash: "always" 配置下,第 1 页的 params.page 是 "",所以首页漏网了。

第三个坑:改内容时顺手改了样式

这个最难承认。

为了让几个页面“风格统一”,我重写了它们的模板。结果把友链页的申请表单、评论区的说明、注意事项条目全删了——因为我在重写时只关注了“视觉是否统一”,没关注“这个页面本来有什么功能”。

净删了 1000 多行,而我当时觉得“更清爽了”。

教训:统一风格应该改样式,不该重写结构。如果只需要换外观,改类名就够了;真要重写,先把原文件的完整内容读一遍。

现在的做法

  1. 令牌集中在单文件,颜色一律引用变量,不允许硬编码
  2. 构建后做产物校验——不看“编译通过”,而是去产物 HTML 里确认关键结构真的存在:
const ok = new RegExp(`${token}\\s*:`).test(cssHtml);
console.log(ok ? "OK" : "MISS", token);
  1. 改动前先看 diff 规模。删了 300 行却只为了“统一风格”,这本身就是危险信号:
git diff --stat

一点感想

技术债最麻烦的地方不在于它存在,而在于它看起来是正常的。

这个博客的样式问题藏了不知道多久,期间我还写了好几篇技术教程,教别人怎么配置 Cloudflare 和 EdgeOne。挺讽刺的——自己的站点 CSS 变量都没定义,却在教别人配 DNS。

不过也正是这件事让我想明白一个道理:越是“看起来能跑”的系统,越需要主动验证。构建不报错、页面能打开、点起来没毛病——这些都不构成“它是正确的”证据。

真正构成证据的是:你知道它为什么对。