Web Vitals 数据需主动监听,用 web-vitals 库早期注入脚本,配合 sendBeacon 上报,后端分层存储并分级告警,监控脚本须防抖、采样、及时销毁观察器。

Web Vitals 数据怎么从浏览器里捞出来
核心指标(LCP、FID、CLS、INP)不会自动上报,必须主动监听。用 web-vitals 官方库最稳,它封装了 PerformanceObserver 的兼容逻辑,避免自己处理 entryType 过滤和 polyfill 问题。
关键点:必须在页面加载早期(比如 内)注入脚本,否则会漏掉首屏关键指标。不要等 DOMContentLoaded 再初始化 —— LCP 可能在 DOM 加载前就触发了。
-
getLCP()和getINP()默认只上报“最终值”,如需诊断过程数据(比如 CLS 累积过程),得传{ reportAllChanges: true } - 注意
FID已被INP取代,但老版本浏览器仍需兼容;web-vitals@3.0+默认只发INP,若需双报,得手动调用onFID()+onINP() - 移动端 WebView(尤其是 iOS WKWebView)可能不支持
layout-shiftentry,CLS会 fallback 为 0,需结合visibilitychange补充采样
怎么把指标安全可靠地送到后端
不能用 fetch 或 XMLHttpRequest 直接发,页面卸载时请求大概率被浏览器终止(尤其 iOS Safari)。必须用 navigator.sendBeacon(),它专为离页上报设计,底层走的是异步 HTTP 请求,不阻塞卸载流程。
但 sendBeacon() 有硬限制:只支持 POST,且 payload 必须是 ArrayBuffer、Blob 或 FormData —— 不能直接传 JSON 字符串。
立即学习“前端免费学习笔记(深入)”;
- 推荐序列化为
URLSearchParams:比Blob兼容性好,服务端也容易解析;sendBeacon('/log', new URLSearchParams(data)) - 单次上报别塞太多字段,
sendBeacon在某些低端安卓机上有 64KB 限制;关键字段保留url、navigationType(reload/back_forward)、deviceMemory、effectiveType - 加个简单重试机制:首次失败(比如网络断开)时存到
localStorage,下次页面打开时补发;但别持久化太久,避免堆积脏数据
后端接收和存储要注意什么
前端上报的指标是原始值(比如 LCP 是毫秒数,CLS 是小数),直接入库会导致聚合困难。必须在写入前做标准化处理:打标设备类型、网络环境、页面分组,并按 Web Vitals 官方阈值分级(good/needs-improvement/poor)。
别用单张大宽表存所有字段——查询慢、索引难建。建议分层存储:
- 明细层:存原始上报(
timestamp、url、cls_value、inp_value等),用时序数据库(如 TimescaleDB)或带分区的 MySQL - 聚合层:每 5 分钟跑一次任务,按
pageKey+effectiveType统计 P75/P95,写入单独汇总表 - 告警层:对连续 10 分钟
P95 LCP > 2500ms的页面触发钉钉/企微通知,避免只看平均值掩盖长尾问题
如何避免监控系统本身拖慢业务
监控脚本不是旁观者,它本身也是页面 JS 的一部分。常见陷阱:监听太多 PerformanceObserver 类型、频繁计算 layout shift、无节制打日志。
- 只监听必需的
entryType:largest-contentful-paint、interaction、layout-shift;别加paint或navigation,它们和 Web Vitals 无关且开销大 -
CLS计算要防抖:原生LayoutShift事件可能高频触发(比如动画帧),用requestIdleCallback延迟到空闲期再聚合,避免卡主线程 - 开发环境默认关闭所有上报,用
localStorage.setItem('VITALS_DEBUG', 'true')手动开启;生产环境加采样率(比如Math.random() )控制流量
真实瓶颈往往不在指标采集,而在页面 onload 后还持续监听 —— 比如忘记取消 INP 的 interaction 观察器,导致内存泄漏。每次注册都要配对 observer.disconnect()。



















