KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA
09 · 性能与 CWV 工程化:把指标变成门禁 — keel 龙骨
本章目标:把 LCP/INP/CLS 从"事后看报告"变成"上线前的预算与门禁"。 验收标准:发版流水线能在性能预算超标时自动拦截,且区分实验室数据与真实用户数据。
本章目标:把 LCP/INP/CLS 从"事后看报告"变成"上线前的预算与门禁"。
验收标准:发版流水线能在性能预算超标时自动拦截,且区分实验室数据与真实用户数据。
为什么性能要工程化:既有《SEO 收录与优化》第 07 章讲了 CWV 是什么、怎么达标。但那是一次性的调优。真正的问题是它会不会回退——一次不经意的依赖升级就能把 LCP 打回原形。解决办法只有一个:把预算写进流水线,让它自己守门。
一、三个核心指标与阈值
| 指标 | 含义 | 良好 | 需改进 | 差 |
|---|---|---|---|---|
| LCP | 最大内容绘制 | ≤ 2.5s | 2.5–4.0s | > 4.0s |
| INP | 交互到下次绘制 | ≤ 200ms | 200–500ms | > 500ms |
| CLS | 累计布局偏移 | ≤ 0.1 | 0.1–0.25 | > 0.25 |
判据口径:CWV 按第 75 百分位评估,不是平均值。也就是说,仍有 25% 的访问可以慢——但整体第 75 百分位必须达标。
二、真实用户数据 vs 实验室数据
两者测的不是一回事,必须分清:
| 维度 | 真实用户数据(CrUX / RUM) | 实验室数据(Lighthouse / WebPageTest) |
|---|---|---|
| 来源 | 真实访客的浏览器 | 受控环境的一次运行 |
| 覆盖 | 全量、按设备/网络分层 | 单次、条件固定 |
| 用途 | 排名信号依据 | 定位问题、做回归 |
| 特点 | 滞后(28 天滚动)、不可复现 | 即时、可复现 |
| 盲区 | 低流量页无数据 | 不能代表真实用户 |
核心取舍:
- 实验室数据用于"改":定位到哪块资源、哪个脚本拖慢了 LCP。
- 真实数据用于"判":判断是否达标、是否真的影响了用户与排名。
- 两者冲突时:以真实数据为达标依据,以实验室数据为优化方向。
# 用 Lighthouse CI 在本地/CI 生成实验室数据
npx lighthouse https://www.xxxxxx.cn/learning/ \
--only-categories=performance \
--output=json --output-path=/tmp/lh.json --chrome-flags="--headless"
node -e "const r=require('/tmp/lh.json');
const a=r.audits;
console.log('LCP', a['largest-contentful-paint'].numericValue.toFixed(0),'ms');
console.log('CLS', a['cumulative-layout-shift'].numericValue.toFixed(3));
console.log('TBT', a['total-blocking-time'].numericValue.toFixed(0),'ms');"
三、把指标变成预算(Performance Budget)
预算 = 明确的上限数字,超标即失败。
| 预算项 | 上限(示例) | 依据 |
|---|---|---|
| LCP | 2.5s(实验室同口径) | 达标线 |
| CLS | 0.1 | 达标线 |
| 首屏 JS | 170KB(gzip) | 影响 INP/LCP |
| 首屏 CSS | 50KB | 影响渲染阻塞 |
| 图片单张 | 200KB | 影响 LCP |
| 总请求数 | 50 | 影响加载 |
| TTFB | 200ms | 服务端 |
// budget.json —— 交给 Lighthouse CI
[
{
"path": "/*",
"timings": [
{ "metric": "largest-contentful-paint", "budget": 2500 },
{ "metric": "cumulative-layout-shift", "budget": 0.1 },
{ "metric": "total-blocking-time", "budget": 300 }
],
"resourceSizes": [
{ "resourceType": "script", "budget": 170 },
{ "resourceType": "image", "budget": 300 },
{ "resourceType": "total", "budget": 800 }
]
}
]
四、接进流水线(门禁)
# .github/workflows/perf.yml(示意)
- name: Lighthouse CI
run: |
npm i -g @lhci/cli
lhci autorun --collect.url=https://www.xxxxxx.cn/learning/ \
--assert.budgetsFile=budget.json
# 无 CI 环境时的本地门禁脚本
#!/usr/bin/env bash
set -e
npx lighthouse https://www.xxxxxx.cn/learning/ \
--only-categories=performance --output=json --output-path=/tmp/lh.json \
--chrome-flags="--headless" --quiet
LCP=$(node -e "console.log(require('/tmp/lh.json').audits['largest-contentful-paint'].numericValue)")
CLS=$(node -e "console.log(require('/tmp/lh.json').audits['cumulative-layout-shift'].numericValue)")
awk -v l=$LCP -v c=$CLS 'BEGIN{
if (l>2500){print "❌ LCP 超标: "l"ms"; exit 1}
if (c>0.1){print "❌ CLS 超标: "c; exit 1}
print "✅ 性能预算通过 LCP="l"ms CLS="c
}'
门禁的意义:性能和 SEO 其他环节一样,不得回退。接进 CI 后,第 12 章的治理框架才有强制力。
五、常见回退原因与预防
| 回退原因 | 表现 | 预防 |
|---|---|---|
| 依赖升级 | JS 体积暴涨 | 体积预算 + bundle 分析 |
| 新增第三方脚本 | TBT/INP 上升 | 脚本白名单 + 总预算 |
| 图片未优化 | LCP 变差 | 构建期图片压缩与格式转换 |
| 字体加载 | CLS/闪烁 | 预加载 + font-display |
| 布局后插内容 | CLS 上升 | 预留尺寸 |
| 服务端变慢 | TTFB 上升 | 服务端监控 |
# 构建产物体积检查(示例,配合 webpack/vite 输出)
du -sh frontend/dist/assets/*.js | sort -h | tail -5
六、故障速查
| 现象 | 性能层原因 | 处理 |
|---|---|---|
| LCP 超标 | 大图/字体/阻塞脚本 | 定位 LCP 元素并优化 |
| INP 超标 | 长任务、重 JS | 拆任务、减脚本 |
| CLS 超标 | 无尺寸的图片/广告 | 预留空间 |
| 实验室达标真实不达标 | 样本偏差 | 看 CrUX/RUM 分层数据 |
| 上版后性能回退 | 依赖/脚本变化 | 检查门禁是否生效 |
七、本章验收
- 定义了 LCP/INP/CLS 与资源体积的预算数字
- 有 Lighthouse CI 或本地门禁脚本,超标即失败
- 门禁接入了发版流程
- 能区分实验室数据(改)与真实数据(判)
- 有回退原因清单与预防手段
# 本章核心:一次性能门禁检查
npx lighthouse https://www.xxxxxx.cn/learning/ --only-categories=performance \
--output=json --output-path=/tmp/lh.json --chrome-flags="--headless" --quiet
性能守住了,接下来要能证明"守住的东西确实有效"——即度量与归因。