Web Vitals Measurement Library
The `web-vitals` library provides a lightweight, modular way to measure essential performance metrics, known as Web Vitals, directly in the browser. It focuses on the current Core Web Vitals (Largest Contentful Paint (LCP), Interaction to Next Paint (INP), and Cumulative Layout Shift (CLS)), as well as First Contentful Paint (FCP) and Time to First Byte (TTFB). Maintained by the Google Chrome team, this library ensures that the collected data accurately reflects how these metrics are measured by Chrome and reported in official Google tools like the Chrome User Experience Report. It is widely used for Real User Monitoring (RUM) to collect and send performance data to analytics endpoints. The library is actively developed, with its current stable version being 5.2.0, and releases generally align with updates to Web Vitals definitions and Chrome's underlying measurement methodologies.
Common errors
-
ReferenceError: document is not defined
cause The `web-vitals` library relies on browser-specific APIs (like `document`, `window`, `PerformanceObserver`) and is not intended to run in Node.js environments.fixEnsure `web-vitals` is only imported and executed in a browser context. If using a server-side rendering (SSR) framework, conditionally import or execute the `web-vitals` code only on the client side (e.g., inside `useEffect` in React, or within `window.onload` checks). -
TypeError: onFID is not a function
cause You are attempting to use the `onFID` function which was removed in `web-vitals` v5.0.0. Interaction to Next Paint (INP) replaced First Input Delay (FID) as a Core Web Vital.fixUpdate your code to use `onINP` instead of `onFID`. You may also need to upgrade your `web-vitals` package to a version that supports `onINP` if you are on a very old version (though `onINP` has been present for a while). -
Web Vitals metrics are not appearing in my analytics dashboard.
cause The `web-vitals` library measures metrics but does not send them to an analytics service by itself. You must provide a callback function that sends the collected `Metric` object to your chosen analytics endpoint.fixImplement a `sendToAnalytics` function (as shown in the quickstart example) and pass it to each `onXXX` function. Ensure this function correctly formats and dispatches the data to your analytics provider (e.g., Google Analytics, custom API) and that the endpoint is reachable and correctly configured to receive the data. Consider using `navigator.sendBeacon` for improved reliability.
Warnings
- breaking The `onFID()` function was deprecated in v3/v4 and completely removed in v5. It has been superseded by `onINP()` (Interaction to Next Paint) as the primary responsiveness metric in Core Web Vitals as of March 2024.
- breaking All `getXXX()` functions (e.g., `getCLS`, `getLCP`) were deprecated in v3 and removed in v4. These functions were designed for one-off measurement, whereas the `onXXX()` functions provide more accurate real-time field data by reporting updates throughout the page lifecycle.
- breaking The browser support policy changed in v5 to 'Baseline Widely available', potentially impacting older browser versions. This means features are supported in browsers that have 95% or greater global usage for at least 6 months.
- breaking When using the `/attribution` build, several fields within the attribution objects for LCP, INP, and TTFB metrics were renamed in v4 and v5. For example, `eventTarget` to `interactionTarget` for INP, and various `*Time` fields to `*Duration` for LCP/TTFB.
- gotcha Metrics like CLS and INP can update multiple times throughout the page's lifecycle. The `onXXX()` functions will call your callback whenever the metric's value changes or is finalized. Ensure your analytics integration is designed to handle these updates, potentially reporting the latest value (or delta) upon page unload or visibility change.
Install
-
npm install web-vitals -
yarn add web-vitals -
pnpm add web-vitals
Imports
- onLCP, onINP, onCLS
const { onLCP } = require('web-vitals');import { onLCP, onINP, onCLS } from 'web-vitals'; - onFCP, onTTFB
import onFCP from 'web-vitals/onFCP';
import { onFCP, onTTFB } from 'web-vitals'; - onLCP, onINP, onCLS (Attribution build)
import { onLCP } from 'web-vitals';import { onLCP, onINP, onCLS } from 'web-vitals/attribution';
Quickstart
import { onCLS, onFCP, onINP, onLCP, onTTFB, type Metric } from 'web-vitals';
interface AnalyticsData {
name: string;
value: number;
id: string;
navigationType: string;
delta?: number;
entries?: PerformanceEntry[];
}
function sendToAnalytics(metric: Metric) {
const analyticsEndpoint = process.env.ANALYTICS_URL ?? '/api/analytics';
const data: AnalyticsData = {
name: metric.name,
value: metric.value,
id: metric.id,
navigationType: metric.navigationType,
delta: metric.delta,
entries: metric.entries // Include entries for detailed analysis if needed
};
// Use sendBeacon for reliable reporting on page unload
(navigator.sendBeacon && navigator.sendBeacon(analyticsEndpoint, JSON.stringify(data))) ||
fetch(analyticsEndpoint, {
body: JSON.stringify(data),
method: 'POST',
credentials: 'omit',
keepalive: true, // Ensures request completes even if page unloads
headers: {
'Content-Type': 'application/json'
}
}).catch(error => console.error('Failed to send analytics:', error));
console.log(`Metric Reported: ${metric.name} - Value: ${metric.value} (ID: ${metric.id})`);
}
// Measure all Core Web Vitals and other key metrics, sending them to analytics
onCLS(sendToAnalytics);
onFCP(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
onTTFB(sendToAnalytics);
console.log('Web Vitals measurement initialized.');