# `web-vitals` - [Overview](#overview) - [Installation](#installation) - [Usage](#usage) - [Log the results to the console](#log-the-results-to-the-console) - [Report the value on every change](#report-the-value-on-every-change) - [Report only the delta of changes](#report-only-the-delta-of-changes) - [Send the results to an analytics endpoint](#send-the-results-to-an-analytics-endpoint) - [Send the results to Google Analytics](#send-the-results-to-google-analytics) - [Send the results to Google Tag Manager](#send-the-results-to-google-tag-manager) - [Load `web-vitals` from a CDN](#load-web-vitals-from-a-cdn) - [API](#api) - [Types](#types) - [Functions](#functions) - [Development](#development) - [Browser Support](#browser-support) ## Overview The `web-vitals` library is a tiny (~1K), modular library for measuring all the [Web Vitals](https://web.dev/vitals/) metrics on real users, in a way that accurately matches how they're measured by Chrome and reported to other Google tools (e.g. [Chrome User Experience Report](https://developers.google.com/web/tools/chrome-user-experience-report), [Page Speed Insights](https://developers.google.com/speed/pagespeed/insights/), [Search Console's Speed Report](https://webmasters.googleblog.com/2019/11/search-console-speed-report.html)). The library supports all of the [Core Web Vitals](https://web.dev/vitals/#core-web-vitals) as well as all of the [other Web Vitals](https://web.dev/vitals/#other-web-vitals) that can be measured [in the field](https://web.dev/user-centric-performance-metrics/#how-metrics-are-measured): ### Core Web Vitals - [Cumulative Layout Shift (CLS)](https://web.dev/cls/) - [First Input Delay (FID)](https://web.dev/fid/) - [Largest Contentful Paint (LCP)](https://web.dev/lcp/) ### Other Web Vitals - [First Contentful Paint (FCP)](https://web.dev/fcp/) - [Time to First Byte (TTFB)](https://web.dev/time-to-first-byte/) ## Installation You can install this library from npm by running: ```sh npm install web-vitals ``` _**Note:** If you're not using npm, you can still load `web-vitals` via ` ``` ```html ``` _**Note:** it's safe to use module scripts in legacy browsers because unknown script types are ignored._ ## API ### Types: #### `Metric` ```ts interface Metric { // The name of the metric (in acronym form). name: 'CLS' | 'FCP' | 'FID' | 'LCP' | 'TTFB'; // The current value of the metric. value: number; // The delta between the current value and the last-reported value. // On the first report, `delta` and `value` will always be the same. delta: number; // A unique ID representing this particular metric that's specific to the // current page. This ID can be used by an analytics tool to dedupe // multiple values sent for the same metric, or to group multiple deltas // together and calculate a total. id: string; // `false` if the value of the metric may change in the future, // for the current page. isFinal: boolean; // Any performance entries used in the metric value calculation. // Note, entries will be added to the array as the value changes. entries: PerformanceEntry[]; } ``` #### `ReportHandler` ```ts interface ReportHandler { (metric: Metric): void; } ``` ### Functions: #### `getCLS()` ```ts type getCLS = (onReport: ReportHandler, reportAllChanges?: boolean) => void ``` Calculates the [CLS](https://web.dev/cls/) value for the current page and calls the `onReport` function once the value is ready to be reported, along with all `layout-shift` performance entries that were used in the metric value calculation. The reported value is a [double](https://heycam.github.io/webidl/#idl-double) (corresponding to a [layout shift value](https://wicg.github.io/layout-instability/#layout-shift-value)). If the `reportAllChanges` param is `true`, the `onReport` function will be called any time a new `layout-shift` performance entry is dispatched, or once the final value of the metric has been determined. _**Important:** unlike other metrics, CLS continues to monitor changes for the entire lifespan of the page—including if the user returns to the page after it's been hidden/backgrounded. However, since browsers often [will not fire additional callbacks once the user has backgrounded a page](https://developers.google.com/web/updates/2018/07/page-lifecycle-api#advice-hidden), `onReport` is always called when the page's visibility state changes to hidden. As a result, the `onReport` function might be called multiple times during the same page load (see [Reporting only the delta of changes](#report-only-the-delta-of-changes) for how to manage this)._ #### `getFCP()` ```ts type getFCP = (onReport: ReportHandler) => void ``` Calculates the [FCP](https://web.dev/fcp/) value for the current page and calls the `onReport` function once the value is ready, along with the relevant `paint` performance entry used to determine the value. The reported value is a `DOMHighResTimeStamp`. #### `getFID()` ```ts type getFID = (onReport: ReportHandler) => void ``` Calculates the [FID](https://web.dev/fid/) value for the current page and calls the `onReport` function once the value is ready, along with the relevant `first-input` performance entry used to determine the value (and optionally the input event if using the [FID polyfill](#fid-polyfill)). The reported value is a `DOMHighResTimeStamp`. _**Important:** since FID is only reported after the user interacts with the page, it's possible that it will not be reported for some page loads._ #### `getLCP()` ```ts type getLCP = (onReport: ReportHandler, reportAllChanges?: boolean) => void ``` Calculates the [LCP](https://web.dev/lcp/) value for the current page and calls the `onReport` function once the value is ready (along with the relevant `largest-contentful-paint` performance entries used to determine the value). The reported value is a `DOMHighResTimeStamp`. If the `reportAllChanges` param is `true`, the `onReport` function will be called any time a new `largest-contentful-paint` performance entry is dispatched, or once the final value of the metric has been determined. #### `getTTFB()` ```ts type getTTFB = (onReport: ReportHandler) => void ``` Calculates the [TTFB](https://web.dev/time-to-first-byte/) value for the current page and calls the `onReport` function once the page has loaded, along with the relevant `navigation` performance entry used to determine the value. The reported value is a `DOMHighResTimeStamp`. Note, this function waits until after the page is loaded to call `onReport` in order to ensure all properties of the `navigation` entry are populated. This is useful if you want to report on other metrics exposed by the [Navigation Timing API](https://w3c.github.io/navigation-timing/). For example, the TTFB metric starts from the page's [time origin](https://www.w3.org/TR/hr-time-2/#sec-time-origin), which means it [includes](https://developers.google.com/web/fundamentals/performance/navigation-and-resource-timing#the_life_and_timings_of_a_network_request) time spent on DNS lookup, connection negotiation, network latency, and unloading the previous document. If, in addition to TTFB, you want a metric that excludes these timings and _just_ captures the time spent making the request and receiving the first byte of the response, you could compute that from data found on the performance entry: ```js import {getTTFB} from 'web-vitals'; getTTFB((metric) => { // Calculate the request time by subtracting from TTFB // everything that happened prior to the request starting. const requestTime = metric.value - metric.entries[0].requestStart; console.log('Request time:', requestTime); }); ``` _**Note:** browsers that do not support `navigation` entries will fall back to using `performance.timing` (with the timestamps converted from epoch time to `DOMHighResTimeStamp`). This ensures code referencing these values (like in the example above) will work the same in all browsers._ ## Browser Support This code has been tested and will run without error in all major browsers as well as Internet Explorer back to version 9 (when transpiled to ES5). However, some of the APIs required to capture these metrics are only available in Chromium-based browsers (e.g. Chrome, Edge, Opera, Samsung Internet). Browser support for each function is as follows: - `getCLS()`: Chromium - `getFCP()`: Chromium - `getFID()`: Chromium, Firefox, Safari, Internet Explorer (with polyfill, [see below](#fid-polyfill)) - `getLCP()`: Chromium - `getTTFB()`: Chromium, Firefox, Safari, Internet Explorer ### FID Polyfill The `getFID()` function will work in all browsers if the page has included the [FID polyfill](https://github.com/GoogleChromeLabs/first-input-delay). Browsers that support the native [Event Timing API](https://wicg.github.io/event-timing/) will use that and report the metric value from the `first-input` performance entry. Browsers that **do not** support the native Event Timing API will use the value reported by the polyfill, and the `entries` array will contain a plain-object version of the native [`PerformanceEventTiming`](https://wicg.github.io/event-timing/#sec-performance-event-timing) object. _**Note:** the `duration` and `processingEnd` properties of the `PerformanceEventTiming` will not be present, as they're not exposed by the polyfill._ ## Development ### Building the code The `web-vitals` source code is written in TypeScript. To transpile the code and build the production bundles, run the following command. ```sh npm run build ``` To build the code and watch for changes, run: ```sh npm run watch ``` ### Running the tests The `web-vitals` code is tested in real browsers using [webdriver.io](https://webdriver.io/). Use the following command to run the tests: ```sh npm test ``` To test any of the APIs manually, you can start the test server ```sh npm run test:server ``` Then navigate to `http://localhost:9090/test/`, where `` is the basename of one the templates under [/test/views/](/test/views/). You'll likely want to combine this with `npm run watch` to ensure any changes you make are transpiled and rebuilt. ## License [Apache 2.0](/LICENSE)