性能监控与Web-Vitals
1. Core Web Vitals(Google 三件套)
| 指标 | 含义 | 良好 | 差 |
|---|---|---|---|
| LCP(Largest Contentful Paint) | 最大内容绘制 | < 2.5s | > 4s |
| INP(Interaction to Next Paint) | 交互响应(取代 FID) | < 200ms | > 500ms |
| CLS(Cumulative Layout Shift) | 累积布局偏移 | < 0.1 | > 0.25 |
辅助:
- FCP(First Contentful Paint):首次内容绘制
- TTFB(Time to First Byte):首字节
- TTI(Time to Interactive):可交互时间
Google 搜索排名考量这些。性能差直接影响 SEO + 转化。
2. 采集
2.1 web-vitals 库
import { onCLS, onINP, onLCP, onFCP, onTTFB } from 'web-vitals'
function send(metric) {
navigator.sendBeacon('/api/rum', JSON.stringify({
name: metric.name,
value: metric.value,
id: metric.id,
rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
url: location.href,
ts: Date.now(),
}))
}
onLCP(send)
onINP(send)
onCLS(send)
onFCP(send)
onTTFB(send)
2.2 PerformanceObserver(原生)
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.name, entry.duration)
}
}).observe({ type: 'largest-contentful-paint', buffered: true })
3. RUM(Real User Monitoring)
真实用户数据。SaaS:
- Sentry Performance
- DataDog RUM
- New Relic Browser
- Cloudflare Web Analytics
- 阿里 ARMS
自建:采集 → Kafka / Loki → Grafana。
4. Lighthouse / PageSpeed Insights
实验室数据(lab data),单次跑出来的:
npm i -g lighthouse
lighthouse https://example.com --view --preset=desktop
# CI 集成
npm i -g @lhci/cli
lhci autorun
.lighthouserc.json:
{
"ci": {
"collect": { "url": ["https://staging.example.com"] },
"assert": {
"assertions": {
"categories:performance": ["error", { "minScore": 0.8 }],
"first-contentful-paint": ["warn", { "maxNumericValue": 2000 }]
}
}
}
}
5. 性能预算(Performance Budget)
CI 里强制:
// vite-bundle-analyzer / webpack-bundle-analyzer
// + size-limit
{
"size-limit": [
{
"path": "dist/index-*.js",
"limit": "200 KB"
},
{
"path": "dist/vendor-*.js",
"limit": "300 KB"
}
]
}
npx size-limit
PR 超预算 CI 失败,强制讨论。
6. 长任务监控
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// 任务超过 50ms 即长任务
sendLongTask({
duration: entry.duration,
name: entry.name,
attribution: entry.attribution, // Chrome 才有
})
}
}).observe({ type: 'longtask', buffered: true })
INP 高的根因 = 长任务。
7. 资源加载监控
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 1000) {
// 慢资源(> 1s)
sendSlowResource({
name: entry.name,
type: entry.initiatorType,
size: entry.transferSize,
duration: entry.duration,
})
}
}
}).observe({ type: 'resource', buffered: true })
8. 错误与性能联动
Sentry 把 LCP / INP 关联到 transaction,能看到"慢页面对应哪些组件渲染"。
9. 优化方向(按指标)
9.1 LCP 慢
- 服务端 TTFB 慢 → SSR 优化、CDN
- 阻塞渲染资源 → preload 关键 CSS、defer 非关键 JS
- 大图 → WebP / AVIF + responsive
- 字体 FOUT →
font-display: swap+ preload
9.2 INP 慢
- 长任务太多 → 切片(
requestIdleCallback、Web Worker) - React 大列表 → 虚拟滚动
- 输入响应慢 → debounce / 节流
9.3 CLS 高
- 图片 / 视频不设 width/height
- 动态插入广告 / 横幅
- 字体加载导致重排(用 size-adjust)
10. 监控告警
- alert: LCPDegraded
expr: histogram_quantile(0.75, sum(rate(rum_lcp_bucket[10m])) by (le, page)) > 3000
for: 10m
annotations:
summary: "页面 }} $labels.page }} P75 LCP > 3s"
按页面、设备类型、地域分维度看。
11. 常见反模式
- 只看 lab 数据不看 RUM:lab 漂亮真实用户慢
- 采样率 100%:数据量爆炸
- 不分页面看:不同页面性能差异巨大,平均无意义
- 不分 P50 / P75 / P95:均值掩盖长尾
- 改了不验证:发版后不看新数据
- CLS 只在桌面测:移动端 CLS 完全不同
- 大图懒加载首屏:LCP 反而变慢(首屏图应该 eager + preload)