web-vitals
Google 官方的 JavaScript 库,用于在浏览器中采集 Core Web Vitals(LCP、INP、CLS)等关键 Web 性能指标,是构建 RUM(Real User Monitoring)方案的基础。
技术简介说明
web-vitals 是 Google Chrome 团队官方维护的轻量 JavaScript 库,提供了一组简洁的 API 来采集 Web 应用的核心性能指标。它是构建自定义 RUM(Real User Monitoring)方案的基础组件——几乎所有主流 APM/RUM 平台(如 Sentry、Datadog、New Relic 等)都在底层使用这个库来采集 Core Web Vitals。
该库的核心价值在于精确性和一致性 ——它直接实现了浏览器 Performance API 的标准化测量逻辑,确保采集到的指标与 Chrome 浏览器(以及 CrUX 数据集)使用的测量方法完全一致。对于需要理解真实用户性能体验、验证 Core Web Vitals 达标情况、或将性能数据发送到自有分析系统的前端团队来说,web-vitals 是首选的采集层。
截至 2026 年,web-vitals 已达到 v5.3.0 版本,支持所有当前 Core Web Vitals 指标(LCP、INP、CLS)以及辅助诊断指标(FCP、TTFB)。库体积仅约 2KB(brotli 压缩后),零依赖,支持 ESM、CJS 和 UMD 多种模块格式,并提供标准版和 attribution 版两种构建。
基本信息
官网 :https://web.dev/articles/vitals
GitHub :https://github.com/GoogleChrome/web-vitals
License :Apache-2.0
最新版本 :v5.3.0(2026 年 5 月)
主要维护者/公司 :Google Chrome 团队
GitHub Stars :8.5k+
npm 周下载量 :约 2450 万(24,479,249)
包体积 :~2KB(brotli 压缩)
零依赖 :无第三方依赖
TypeScript :完整类型定义支持
快速上手
安装
# npm
npm install web-vitals
# yarn
yarn add web-vitals
# pnpm
pnpm add web-vitals
# CDN
< script type = "module" >
import { onLCP, onINP, onCLS } from 'https://unpkg.com/web-vitals@5/dist/web-vitals.es.js' ;
< /script >
基础配置
// 导入所需指标的测量函数
import { onCLS, onINP, onLCP, onFCP, onTTFB } from 'web-vitals' ;
// 注册回调函数,当指标可用时触发
onCLS (sendToAnalytics);
onINP (sendToAnalytics);
onLCP (sendToAnalytics);
onFCP (sendToAnalytics);
onTTFB (sendToAnalytics);
// 发送数据到你的分析服务
function sendToAnalytics ( metric ) {
// metric 对象包含:
// - name: 指标名称(如 'CLS', 'INP', 'LCP')
// - value: 指标值
// - rating: 'good' | 'needs-improvement' | 'poor'
// - delta: 与上次值的差异
// - id: 页面生命周期 ID
// - navigationType: 'navigate' | 'reload' | 'back-forward' 等
// - entries: 底层 PerformanceEntry 数组
const body = JSON . stringify ({
name: metric.name,
value: metric.value,
rating: metric.rating,
delta: metric.delta,
id: metric.id,
navigationType: metric.navigationType,
url: window.location.href,
});
// 使用 sendBeacon 确保数据在页面卸载时也能发送
if (navigator.sendBeacon) {
navigator. sendBeacon ( '/analytics' , body);
}
}
最小示例
import { onLCP, onINP, onCLS } from 'web-vitals' ;
// 最简单的用法:将 Core Web Vitals 发送到控制台
onLCP (console.log);
onINP (console.log);
onCLS (console.log);
使用 reportAllChanges 选项
import { onLCP, onINP, onCLS } from 'web-vitals' ;
// reportAllChanges: true 会在指标值每次变化时触发回调(而不仅是最终值)
onLCP (console.log, { reportAllChanges: true });
onINP (console.log, { reportAllChanges: true });
onCLS (console.log, { reportAllChanges: true });
使用 Attribution 构建(诊断模式)
// Attribution 构建包含额外的诊断信息
import { onLCP, onINP, onCLS } from 'web-vitals/attribution' ;
onLCP (( metric ) => {
console. log ( 'LCP:' , metric.value);
console. log ( 'LCP Element:' , metric.attribution.element);
console. log ( 'LCP URL:' , metric.attribution.url);
console. log ( 'LCP Time to First Byte:' , metric.attribution.timeToFirstByte);
console. log ( 'LCP Resource Load Delay:' , metric.attribution.resourceLoadDelay);
console. log ( 'LCP Resource Load Time:' , metric.attribution.resourceLoadTime);
console. log ( 'LCP Element Render Delay:' , metric.attribution.elementRenderDelay);
});
onINP (( metric ) => {
console. log ( 'INP:' , metric.value);
console. log ( 'Event Type:' , metric.attribution.eventType);
console. log ( 'Interaction Target:' , metric.attribution.interactionTarget);
console. log ( 'Interaction Target Selector:' , metric.attribution.interactionTargetSelector);
console. log ( 'Interaction Time:' , metric.attribution.interactionTime);
console. log ( 'Input Delay:' , metric.attribution.inputDelay);
console. log ( 'Processing Duration:' , metric.attribution.processingDuration);
console. log ( 'Presentation Delay:' , metric.attribution.presentationDelay);
});
onCLS (( metric ) => {
console. log ( 'CLS:' , metric.value);
console. log ( 'Largest Shift Target:' , metric.attribution.largestShiftTarget);
console. log ( 'Largest Shift Selector:' , metric.attribution.largestShiftSelector);
console. log ( 'Largest Shift Value:' , metric.attribution.largestShiftValue);
console. log ( 'Largest Shift Time:' , metric.attribution.largestShiftTime);
});
核心概念与架构
支持的指标
Core Web Vitals(核心指标)
指标 全称 衡量维度 良好 需改进 差 LCP Largest Contentful Paint 加载性能 ≤ 2.5s 2.5s–4.0s > 4.0s INP Interaction to Next Paint 交互响应性 ≤ 200ms 200ms–500ms > 500ms CLS Cumulative Layout Shift 视觉稳定性 ≤ 0.1 0.1–0.25 > 0.25
辅助指标
指标 全称 衡量维度 良好 差 FCP First Contentful Paint 首次渲染速度 ≤ 1.8s > 3.0s TTFB Time to First Byte 服务器响应速度 ≤ 800ms > 1.8s
已废弃指标
API 设计
web-vitals 库
│
├── 标准构建 (web-vitals)
│ ├── onLCP(callback, opts?) - 测量 LCP
│ ├── onINP(callback, opts?) - 测量 INP
│ ├── onCLS(callback, opts?) - 测量 CLS
│ ├── onFCP(callback, opts?) - 测量 FCP
│ ├── onTTFB(callback, opts?) - 测量 TTFB
│ └── onNavigation(callback?) - 测量导航类型
│
├── Attribution 构建 (web-vitals/attribution)
│ ├── 包含所有标准 API
│ ├── onLCP → metric.attribution(LCP 分解信息)
│ ├── onINP → metric.attribution(INP 事件链信息)
│ └── onCLS → metric.attribution(CLS 偏移信息)
│
└── 工具函数
├── generateUniqueId() - 生成唯一页面 ID
└── bindReporter() - 绑定报告回调
Metric 对象结构
interface Metric {
// 基本信息
name : 'CLS' | 'FCP' | 'INP' | 'LCP' | 'TTFB' ;
value : number ; // 指标值
delta : number ; // 与上次报告的变化量
id : string ; // 页面生命周期唯一 ID
rating : 'good' | 'needs-improvement' | 'poor' ;
// 导航信息
navigationType : 'navigate' | 'reload' | 'back-forward' |
'back-forward-cache' | 'prerender' ;
// 底层数据
entries : PerformanceEntry []; // 底层 Performance API 条目
// Attribution(仅 attribution 构建)
attribution ?: Attribution ;
}
测量时机
页面加载
↓
FCP 触发 → 回调执行
↓
LCP 触发 → 每次 LCP 候选更新时触发(reportAllChanges: true)
↓
TTFB 触发 → 回调执行(通常在 FCP 之前)
↓
用户交互发生
↓
INP 计算 → 每次交互后更新(reportAllChanges: true)
↓
页面可见性变化(visibilitychange: hidden)
↓
所有指标最终值确定 → 最终回调执行
核心特性
📊 完整 Core Web Vitals 支持 :LCP、INP、CLS 三大核心指标
🔬 Attribution 诊断构建 :包含详细的性能归因信息(元素、URL、时间分解)
📦 超轻量级 :~2KB(brotli),零依赖
🎯 精确测量 :与 Chrome 浏览器和 CrUX 数据集使用相同的测量逻辑
🔄 SPA 支持 :支持客户端导航的指标测量(配合 Soft Navigations API)
📱 多模块格式 :支持 ESM、CJS、UMD 和 IIFE
🛡️ TypeScript 支持 :完整的类型定义
🧪 可测试性 :支持模拟 PerformanceEntry 进行单元测试
📋 reportAllChanges 模式 :可在指标值每次变化时触发回调
🔗 分析集成友好 :回调设计便于与任何分析平台集成
生态图
web-vitals 生态系统
│
├── 核心库
│ ├── web-vitals(标准构建,~2KB)
│ └── web-vitals/attribution(诊断构建)
│
├── 平台集成
│ ├── Google Analytics 4(自动采集)
│ ├── Sentry(错误监控 + 性能)
│ ├── Datadog RUM
│ ├── New Relic Browser
│ ├── SpeedCurve RUM
│ ├── Calibre
│ └── 各种 APM/RUM 工具
│
├── 框架集成
│ ├── Next.js(内置支持)
│ ├── Nuxt(@nuxtjs/web-vitals)
│ ├── Remix
│ └── Astro
│
├── 浏览器扩展
│ └── Web Vitals Extension(Chrome 扩展)
│ └── https://github.com/GoogleChrome/web-vitals-extension
│
└── 社区工具
├── web-vitals-report(可视化工具)
├── SpeedCurve RUM SDK(底层使用 web-vitals)
└── 各种自定义分析集成
关键依赖
零运行时依赖 :web-vitals 不依赖任何第三方库
浏览器 API :依赖 Performance API、PerformanceObserver API
TypeScript :开发时使用,编译后无运行时依赖
适用场景
📊 构建 RUM 方案 :作为自建 RUM(Real User Monitoring)系统的采集层
🔍 性能归因分析 :使用 attribution 构建定位 LCP 元素、INP 事件链等具体瓶颈
📈 上报分析平台 :将 Core Web Vitals 发送到 Google Analytics、Sentry、Datadog 等平台
🏗️ 框架级集成 :在 Next.js、Nuxt 等框架中内置性能指标采集
🧪 A/B 测试 :测量优化方案对 Core Web Vitals 的实际影响
📱 移动端性能监控 :追踪移动端用户的真实性能体验
🔄 SPA 性能追踪 :配合 Soft Navigations API 追踪单页应用客户端导航性能
开发与工程化
React 集成
import { useEffect } from 'react' ;
import { onCLS, onINP, onLCP, onFCP, onTTFB } from 'web-vitals' ;
export function useWebVitals () {
useEffect (() => {
const metrics = {
onCLS, onINP, onLCP, onFCP, onTTFB,
};
Object. entries (metrics). forEach (([ name , fn ]) => {
fn (( metric ) => {
// 发送到你的分析服务
console. log ( `${ name }:` , metric);
sendToAnalytics (metric);
});
});
}, []);
}
function sendToAnalytics ( metric : Metric ) {
// 使用 Google Analytics
if ( typeof window !== 'undefined' && window.gtag) {
const body = {
name: metric.name,
value: Math. round (metric.name === 'CLS' ? metric.value * 1000 : metric.value),
event_category: 'Web Vitals' ,
non_interaction: true ,
};
window. gtag ( 'event' , metric.name, body);
}
}
Google Analytics 4 集成
import { onCLS, onINP, onLCP, onFCP, onTTFB } from 'web-vitals' ;
function sendToGoogleAnalytics ({ name , delta , value , id }) {
// 使用 GA4 的 gtag.js
gtag ( 'event' , name, {
// metric_value 应四舍五入到整数(CLS 乘以 1000)
value: Math. round (name === 'CLS' ? delta * 1000 : delta),
metric_id: id,
metric_value: value,
metric_delta: Math. round (name === 'CLS' ? delta * 1000 : delta),
non_interaction: true , // 避免影响跳出率
});
}
onCLS (sendToGoogleAnalytics);
onINP (sendToGoogleAnalytics);
onLCP (sendToGoogleAnalytics);
onFCP (sendToGoogleAnalytics);
onTTFB (sendToGoogleAnalytics);
Next.js 集成
// app/layout.tsx (Next.js App Router)
import { WebVitals } from './web-vitals' ;
export default function RootLayout ({ children }) {
return (
< html >
< body >
{ children }
< WebVitals />
</ body >
</ html >
);
}
// app/web-vitals.tsx
'use client' ;
import { useReportWebVitals } from 'next/web-vitals' ;
export function WebVitals () {
useReportWebVitals (( metric ) => {
switch (metric.name) {
case 'CLS' :
console. log ( 'CLS:' , metric.value);
break ;
case 'INP' :
console. log ( 'INP:' , metric.value);
break ;
case 'LCP' :
console. log ( 'LCP:' , metric.value);
break ;
case 'FCP' :
console. log ( 'FCP:' , metric.value);
break ;
case 'TTFB' :
console. log ( 'TTFB:' , metric.value);
break ;
}
});
return null ;
}
Sentry 集成
import * as Sentry from '@sentry/browser' ;
import { onCLS, onINP, onLCP } from 'web-vitals' ;
onCLS (( metric ) => {
Sentry.metrics. distribution ( 'web-vitals.cls' , metric.value, {
unit: 'none' ,
tags: {
rating: metric.rating,
},
});
});
onINP (( metric ) => {
Sentry.metrics. distribution ( 'web-vitals.inp' , metric.value, {
unit: 'millisecond' ,
tags: {
rating: metric.rating,
},
});
});
onLCP (( metric ) => {
Sentry.metrics. distribution ( 'web-vitals.lcp' , metric.value, {
unit: 'millisecond' ,
tags: {
rating: metric.rating,
},
});
});
完整的 RUM 集成模板
import { onCLS, onINP, onLCP, onFCP, onTTFB } from 'web-vitals' ;
import { onLCP as onLCPAttr, onINP as onINPAttr, onCLS as onCLSAttr } from 'web-vitals/attribution' ;
// 生成页面唯一 ID
const pageId = generatePageId ();
function generatePageId () {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' . replace ( / [xy] / g , ( c ) => {
const r = (Math. random () * 16 ) | 0 ;
const v = c === 'x' ? r : (r & 0x3 ) | 0x8 ;
return v. toString ( 16 );
});
}
// 批量发送指标
let pendingMetrics = [];
let sendTimer = null ;
function queueMetric ( metric ) {
pendingMetrics. push ({
pageId,
name: metric.name,
value: metric.name === 'CLS' ? Math. round (metric.value * 1000 ) : Math. round (metric.value),
rating: metric.rating,
delta: metric.name === 'CLS' ? Math. round (metric.delta * 1000 ) : Math. round (metric.delta),
id: metric.id,
navigationType: metric.navigationType,
url: window.location.href,
timestamp: Date. now (),
// Attribution 数据(如果有)
attribution: metric.attribution || null ,
});
// 防抖发送(每 5 秒或页面卸载时)
clearTimeout (sendTimer);
sendTimer = setTimeout (flushMetrics, 5000 );
}
function flushMetrics () {
if (pendingMetrics. length === 0 ) return ;
const body = JSON . stringify ({ metrics: pendingMetrics });
if (navigator.sendBeacon) {
navigator. sendBeacon ( '/api/vitals' , body);
} else {
fetch ( '/api/vitals' , {
method: 'POST' ,
body,
keepalive: true ,
headers: { 'Content-Type' : 'application/json' },
});
}
pendingMetrics = [];
}
// 页面卸载时立即发送
document. addEventListener ( 'visibilitychange' , () => {
if (document.visibilityState === 'hidden' ) {
flushMetrics ();
}
});
// 注册所有指标(标准构建)
onCLS (queueMetric);
onINP (queueMetric);
onLCP (queueMetric);
onFCP (queueMetric);
onTTFB (queueMetric);
// 如果需要诊断信息,使用 attribution 构建
// onCLSAttr(queueMetric);
// onINPAttr(queueMetric);
// onLCPAttr(queueMetric);
性能与安全
性能特点
库体积 :~2KB(brotli),~5KB(gzip)
运行时开销 :极低,仅在性能事件触发时执行回调
内存占用 :最小,不存储历史数据
渲染阻塞 :不阻塞页面渲染
CDN 加载 :支持从 CDN 异步加载,不阻塞关键渲染路径
安全注意事项
库本身不包含网络请求逻辑,发送数据由开发者控制
应确保分析端点有适当的数据验证和限频
不要在指标数据中包含可识别用户信息
attribution 数据中可能包含 DOM 选择器,注意 XSS 防护
浏览器兼容性
指标 最低浏览器版本 LCP Chrome 77+, Firefox 122+, Safari 17+ INP Chrome 128+, Firefox 122+, Safari 18+ CLS Chrome 77+, Firefox 117+, Safari 17+ FCP Chrome 60+, Firefox 52+, Safari 11.1+ TTFB 所有现代浏览器
技术对比
特性 web-vitals SpeedCurve RUM Sentry RUM Datadog RUM 自建方案 核心指标 ✅ 完整 ✅ ✅ ✅ 需自行实现 体积 ~2KB 较大 较大 较大 自定义 归因信息 ✅ attribution 构建 ✅ ✅ ✅ 需自行实现 SPA 支持 ✅ ✅ ✅ ✅ 需自行实现 自定义指标 ❌ ✅ ✅ ✅ ✅ 分析集成 需自行对接 内置 内置 内置 自建 仪表板 ❌ ✅ ✅ ✅ 需自建 费用 免费 付费 免费额度 付费 维护成本 数据所有权 自行控制 第三方 第三方 第三方 完全自主
web-vitals vs 完整 RUM 平台
维度 web-vitals 完整 RUM 平台 定位 采集层(轻量库) 完整监控方案 功能范围 仅采集 Core Web Vitals 采集 + 传输 + 存储 + 可视化 数据可视化 无(需自行构建) 内置仪表板 告警 无 内置 适用场景 自建分析、已有 APM 集成 需要开箱即用的完整方案
最佳实践
生产环境建议
使用 Attribution 构建进行诊断 :在开发/预发布环境使用 attribution 构建收集详细归因信息;生产环境使用标准构建减少体积
正确使用 reportAllChanges :
发送到分析平台时使用默认模式(仅最终值)
调试和开发时使用 reportAllChanges: true 追踪变化过程
使用 sendBeacon :页面卸载时使用 navigator.sendBeacon() 确保数据不丢失
batch 发送 :使用队列批量发送指标,减少 HTTP 请求数
CLS 值乘以 1000 :CLS 是小数(如 0.05),上报前乘以 1000 转为整数便于分析
区分导航类型 :navigate、reload、back-forward 等导航类型应分别统计
常见陷阱
❌ 在 SSR 中直接调用 :web-vitals 依赖浏览器 API,不能在服务端执行
❌ 忽略页面卸载事件 :不在 visibilitychange 时 flush 数据会导致丢失
❌ 使用 attribution 构建在生产环境 :attribution 构建体积更大,诊断信息在生产环境通常不需要
❌ 重复注册回调 :在 React 的 useEffect 中忘记清理,导致重复注册
❌ 忽略 SPA 导航 :SPA 客户端导航不会触发新的 LCP/FCP,需要使用 Soft Navigations API
推荐模式
✅ 开发环境 :attribution 构建 + reportAllChanges: true + 控制台输出
✅ 预发布环境 :attribution 构建 + 发送到分析平台 + 告警
✅ 生产环境 :标准构建 + 批量发送 + 仅最终值
✅ 搭配 CrUX :web-vitals 提供实时 RUM 数据,CrUX 提供 Google 搜索排名基准
技术局限与边界
已知限制
局限 详细说明 仅 Chrome 标准化 测量逻辑与 Chrome 浏览器对齐,其他浏览器可能有微小差异 无内置传输层 只负责采集,数据发送需自行实现 无仪表板 不提供数据可视化,需自建或对接分析平台 SPA 导航限制 客户端导航不会触发新的 FCP/LCP(Soft Navigations API 正在解决) 不支持自定义指标 仅支持预定义的 Web Vitals 指标 无法模拟数据 不能在不支持的浏览器中生成模拟指标数据 依赖浏览器 API 需要 Performance API 和 PerformanceObserver 支持
不适用场景
需要完整的监控仪表板和告警(应使用 SpeedCurve、Datadog 等平台)
需要非标准指标采集(如自定义业务指标)
需要跨浏览器一致性保证(不同浏览器实现有微小差异)
需要会话级别用户追踪(web-vitals 是匿名采集)
学习资源
官方文档
教程
社区资源
2026 年现状
最新版本
v5.3.0 (2026 年 5 月):当前稳定版本
TypeScript 6.0 支持 :编译工具链已升级
INP 完全成熟 :INP 作为 Core Web Vital 已全面替代 FID
发展趋势
Soft Navigations API :将 Core Web Vitals 扩展到 SPA 客户端导航场景
Attribution 增强 :attribution 构建持续增加更多诊断维度
INP 优化 :INP 的测量精度和归因能力持续改进
框架集成深化 :Next.js、Nuxt 等框架的内置支持持续完善
AI 辅助分析 :各 RUM 平台基于 web-vitals 数据提供 AI 驱动的性能优化建议
社区活跃度
npm 周下载量 2450 万+,是 Web 性能领域使用最广泛的库之一
GitHub 8.5k+ Stars,持续活跃维护
被几乎所有主流 RUM 平台作为底层采集库使用
Google Chrome 团队持续投入维护