给博客定一个性能预算
性能不是优化出来的,是守住的。与其事后做优化,不如一开始就给页面定一条不许越过的线。
目录
大多数博客在上线那天都是快的。慢是后来发生的事:加了个统计脚本,换了套字体,接入了评论,装了个动画库。每一次都只慢了一点点,一年之后首屏要等三秒。
问题不在于某次改动,而在于没有一条线告诉你什么时候该停下来。
什么是性能预算
性能预算是一组你提前约定、并且愿意为之拒绝功能的数字。它可以是资源体积,也可以是时间指标:
首屏 HTML < 15 KB (gzip)
CSS 总量 < 20 KB (gzip)
首屏 JavaScript < 10 KB (gzip)
网络请求数 < 12
LCP (4G 移动端) < 1.5s
CLS < 0.05
数字本身不神圣,重要的是它是事先定下的。事后定的预算永远等于当前值。
静态博客的预算怎么花
一个纯文本博客的首屏,理论上只需要 HTML 和 CSS。所以真正的问题是:那些额外的 KB 都花到哪去了。
字体是最大的一笔开销
一套中文 Web 字体动辄几 MB,即使做了子集化也常在 200 KB 以上。而且字体加载会带来 FOIT 或 FOUT,两者都影响体感。
系统字体栈的代价是不同设备上观感略有差异,收益是零请求、零布局抖动:
--font-sans:
ui-sans-serif, -apple-system, 'Segoe UI', 'PingFang SC',
'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
如果一定要用自定义字体,至少做到三件事:只加载正文一种字重、用 font-display: swap、把字体文件放在自己的域名下避免额外的 DNS 与 TLS 握手。
JavaScript 要按页计算,不是按站计算
「全站 JS 只有 30 KB」是一句没有意义的话。用户打开的是一个页面,不是一个站。
有意义的问法是:打开一篇文章,浏览器要下载和执行多少 JavaScript?
在这套主题里,一篇文章页的脚本大致是这样分布的:
| 功能 | 体积 | 是否必须 |
|---|---|---|
| 主题初始化(内联) | 约 0.2 KB | 是,防止深色模式闪白 |
| 主题切换按钮 | 约 0.3 KB | 否,可在配置里关掉 |
| 目录滚动高亮 | 约 0.8 KB | 否 |
| 代码块复制 | 约 0.6 KB | 否 |
| 搜索 | 0 KB | 只在搜索页加载 |
| 评论 | 0 KB | 滚动到评论区才加载 |
关键不是每一项多小,而是每一项都能被单独关掉,并且默认不在不需要它的页面上出现。
第三方脚本要算总账
一个常见的错觉是「统计脚本只有 5 KB」。实际成本包括:DNS 解析、TLS 握手、脚本下载、解析执行、以及它自己发起的后续请求。在移动网络上,一个第三方域名的首次连接本身就要 200ms 以上。
如果需要访问统计,优先选择服务端日志分析,或者由托管平台提供的、不需要在页面里插脚本的方案。
让预算自动生效
写在文档里的预算会被忘记,写在 CI 里的不会。
最轻量的做法是在构建后检查产物体积:
// scripts/check-budget.mjs
import { readdir, stat } from 'node:fs/promises';
import { gzipSync } from 'node:zlib';
import { readFile } from 'node:fs/promises';
const LIMITS = { '.css': 20_480, '.js': 10_240 };
const files = await readdir('dist/_astro');
let failed = false;
for (const file of files) {
const ext = file.slice(file.lastIndexOf('.'));
const limit = LIMITS[ext];
if (!limit) continue;
const gzipped = gzipSync(await readFile(`dist/_astro/${file}`)).length;
if (gzipped > limit) {
console.error(`${file}: ${gzipped} B 超出预算 ${limit} B`);
failed = true;
}
}
process.exit(failed ? 1 : 0);
然后挂到构建流程里:
npm run build && node scripts/check-budget.mjs
更完整的方案可以用 Lighthouse CI,它能同时约束体积和 Core Web Vitals,并在 Pull Request 上留下评论。
什么时候可以超支
预算不是禁令,是一个需要解释的门槛。
如果一个功能确实值得,超支是可以接受的 —— 前提是你明确知道自己在花钱买什么。比如为了支持数学公式而引入 KaTeX 的样式表,这是一次有意识的交易,而不是一次意外。
真正危险的从来不是那些经过讨论的超支,而是那些没人注意到的累积。