web-vitals logo

web-vitals

Google 官方的 JavaScript 库,用于在浏览器中采集 Core Web Vitals(LCP、INP、CLS)等关键 Web 性能指标,是构建 RUM(Real User Monitoring)方案的基础。

精选工具Web Vitals性能审计Core Web VitalsRUM

web-vitals 是 Google Chrome 团队官方维护的轻量 JavaScript 库(约 2KB),提供简洁 API 采集 Core Web Vitals 指标,是构建 RUM 方案的基础组件,被几乎所有主流 APM/RUM 平台在底层使用。

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
  • GitHubhttps://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(核心指标)

指标全称衡量维度良好需改进
LCPLargest Contentful Paint加载性能≤ 2.5s2.5s–4.0s> 4.0s
INPInteraction to Next Paint交互响应性≤ 200ms200ms–500ms> 500ms
CLSCumulative Layout Shift视觉稳定性≤ 0.10.1–0.25> 0.25

辅助指标

指标全称衡量维度良好
FCPFirst Contentful Paint首次渲染速度≤ 1.8s> 3.0s
TTFBTime to First Byte服务器响应速度≤ 800ms> 1.8s

已废弃指标

指标状态替代
FID已移除(v4.0+)INP

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 防护

浏览器兼容性

指标最低浏览器版本
LCPChrome 77+, Firefox 122+, Safari 17+
INPChrome 128+, Firefox 122+, Safari 18+
CLSChrome 77+, Firefox 117+, Safari 17+
FCPChrome 60+, Firefox 52+, Safari 11.1+
TTFB所有现代浏览器

技术对比

特性web-vitalsSpeedCurve RUMSentry RUMDatadog RUM自建方案
核心指标✅ 完整需自行实现
体积~2KB较大较大较大自定义
归因信息✅ attribution 构建需自行实现
SPA 支持需自行实现
自定义指标
分析集成需自行对接内置内置内置自建
仪表板需自建
费用免费付费免费额度付费维护成本
数据所有权自行控制第三方第三方第三方完全自主

web-vitals vs 完整 RUM 平台

维度web-vitals完整 RUM 平台
定位采集层(轻量库)完整监控方案
功能范围仅采集 Core Web Vitals采集 + 传输 + 存储 + 可视化
数据可视化无(需自行构建)内置仪表板
告警内置
适用场景自建分析、已有 APM 集成需要开箱即用的完整方案

最佳实践

生产环境建议

  1. 使用 Attribution 构建进行诊断:在开发/预发布环境使用 attribution 构建收集详细归因信息;生产环境使用标准构建减少体积
  2. 正确使用 reportAllChanges
    • 发送到分析平台时使用默认模式(仅最终值)
    • 调试和开发时使用 reportAllChanges: true 追踪变化过程
  3. 使用 sendBeacon:页面卸载时使用 navigator.sendBeacon() 确保数据不丢失
  4. batch 发送:使用队列批量发送指标,减少 HTTP 请求数
  5. CLS 值乘以 1000:CLS 是小数(如 0.05),上报前乘以 1000 转为整数便于分析
  6. 区分导航类型navigatereloadback-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

发展趋势

  1. Soft Navigations API:将 Core Web Vitals 扩展到 SPA 客户端导航场景
  2. Attribution 增强:attribution 构建持续增加更多诊断维度
  3. INP 优化:INP 的测量精度和归因能力持续改进
  4. 框架集成深化:Next.js、Nuxt 等框架的内置支持持续完善
  5. AI 辅助分析:各 RUM 平台基于 web-vitals 数据提供 AI 驱动的性能优化建议

社区活跃度

  • npm 周下载量 2450 万+,是 Web 性能领域使用最广泛的库之一
  • GitHub 8.5k+ Stars,持续活跃维护
  • 被几乎所有主流 RUM 平台作为底层采集库使用
  • Google Chrome 团队持续投入维护