Best practices for measuring Web Vitals in the field

How to measure Web Vitals with your current analytics tool.

Having the ability to measure and report on the real-world performance of your pages is critical for diagnosing and improving performance over time. Without field data, it's impossible to know for sure whether the changes you're making to your site are actually achieving their desired results.

Many popular Real User Monitoring (RUM) analytics providers already support the Core Web Vitals metrics in their tools (as well as many other Web Vitals). If you're currently using one of these RUM analytics tools, you're in great shape to assess how well the pages on your site meet the recommended Core Web Vitals thresholds and prevent regressions in the future.

While we do recommend using an analytics tool that supports the Core Web Vitals metrics, if the analytics tool you're currently using does not support them, you don't necessarily need to switch. Almost all analytics tools offer a way to define and measure custom metrics or events, which means you can likely use your current analytics provider to measure the Core Web Vitals metrics and add them to your existing analytics reports and dashboards.

This guide discusses best practices for measuring Core Web Vitals metrics (or any custom metrics) with a third-party or in-house analytics tool. It can also serve as a guide for analytics vendors wishing to add Core Web Vitals support to their service.

Use custom metrics or events

As mentioned above, most analytics tools let you measure custom data. If your analytics tool supports this, you should be able to measure each of the Core Web Vitals metrics using this mechanism.

Measuring custom metrics or events in an analytics tool is generally a three-step process:

  1. Define or register the custom metric in your tool's admin (if required). (Note: not all analytics providers require custom metrics to be defined ahead of time.)
  2. Compute the value of the metric in your frontend JavaScript code.
  3. Send the metric value to your analytics backend, ensuring the name or ID matches what was defined in step 1 (again, if required).

For steps 1 and 3, you can refer to your analytics tool's documentation for instructions. For step 2 you can use the web-vitals JavaScript library to compute the value of each of the Core Web Vitals metrics.

The following code sample shows how easy it can be to track these metrics in code and send them to an analytics service.

import {onCLS, onINP, onLCP} from 'web-vitals';

function sendToAnalytics({name, value, id}) {
  const body = JSON.stringify({name, value, id});
  // Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
  (navigator.sendBeacon && navigator.sendBeacon('/analytics', body)) ||
      fetch('/analytics', {body, method: 'POST', keepalive: true});


Avoid averages

It's tempting to sum up a range of values for a performance metric by calculating an average. Averages seem convenient at first glance, as they're a tidy summary of a large quantity of data, but you should resist the urge to rely on them to interpret page performance.

Averages are problematic because they don't represent any single user's session. Outliers at either range of the distribution may skew the average in ways that are misleading.

For example, a small group of users may be on extremely slow networks or devices that are toward the maximum range of values, but don't account for enough user sessions to impact the average in a way that suggests there's a problem.

Whenever possible, rely on percentiles instead of averages. Percentiles across a distribution for a given performance metric better describe the full range of user experiences for your website. This allows you to focus on subsets of actual experiences, which will give you more insight than a single value ever could.

Ensure you can report a distribution

Once you've computed the values for each of the Core Web Vitals metrics and sent them to your analytics service using a custom metric or event, the next step is to build a report or dashboard displaying the values that have been collected.

To ensure you're meeting the recommended Core Web Vitals thresholds, you'll need your report to display the value of each metric at the 75th percentile.

If your analytics tool does not offer quantile reporting as a built-in feature, you can probably still get this data manually by generating a report that lists every metric value sorted in ascending order. Once this report is generated, the result that is 75% of the way through the full, sorted list of all values in that report will be the 75th percentile for that metric—and this will be the case no matter how you segment your data (by device type, connection type, country, etc.).

If your analytic tool does not give you metric-level reporting granularity by default, you can probably achieve the same result if your analytics tool supports custom dimensions. By setting a unique, custom dimension value for each individual metric instance you track, you should be able to generate a report, broken down by individual metric instances, if you include the custom dimension in the report configuration. Since each instance will have a unique dimension value, no grouping will occur.

The Web Vitals Report is an example of this technique that uses Google Analytics. The code for the report is open source, so developers can reference it as an example of the techniques outlined in this section.

Screenshots of the Web Vitals

Send your data at the right time

Some performance metrics can be calculated once the page has finished loading, while others (like CLS) consider the entire lifespan of the page—and are only final once the page has started unloading.

This can be problematic, however, since both the beforeunload and unload events are not reliable (especially on mobile) and their use is not recommended (since they can prevent a page from being eligible for the Back-Forward Cache).

For metrics that track the entire lifespan of a page, it's best to send whatever the metric's current value is during the visibilitychange event, whenever the page's visibility state changes to hidden. This is because—once the page's visibility state changes to hidden—there's no guarantee that any script on that page will be able to run again. This is especially true on mobile operating systems where the browser app itself can be closed without any page callbacks being fired.

Note that mobile operating systems do generally fire the visibilitychange event when switching tabs, switching apps, or closing the browser app itself. They also fire the visibilitychange event when closing a tab or navigating to a new page. This makes the visibilitychange event far more reliable than the unload or beforeunload events.

Monitor performance over time

Once you've updated your analytics implementation to both track and report on the Core Web Vitals metrics, the next step is to track how changes to your site affect performance over time.

Version your changes

A naive (and ultimately unreliable) approach to tracking changes is to deploy changes to production and then assume that all metrics received after the deployment date correspond to the new site and all metrics received before the deployment date correspond to the old site. However, any number of factors (including caching at the HTTP, service worker, or CDN layer) can prevent this from working.

A much better approach is to create a unique version for each deployed change and then track that version in your analytics tool. Most analytics tools support setting a version. If yours does not, you can create a custom dimension and set that dimension to your deployed version.

Run experiments

You can take versioning one step further by tracking multiple versions (or experiments) at the same time.

If your analytics tool lets you define experiment groups, use that feature. Otherwise, you can use custom dimensions to ensure each of your metric values can be associated with a particular experiment group in your reports.

With experimentation in place in your analytics, you can roll out an experimental change to a subset of your users and compare the performance of that change to the performance of users in the control group. Once you have confidence that a change does indeed improve performance, you can roll it out to all users.

Ensure measurement doesn't affect performance

When measuring performance on real users, it's absolutely critical that any performance measurement code you're running does not negatively impact the performance of your page. If it does, then any conclusions you attempt to draw on how your performance affects your business will be unreliable, as you'll never know if the presence of the analytics code itself is having the largest negative impact.

Always follow these principles when deploying RUM analytics code on your production site:

Defer your analytics

Analytics code should always be loaded in an asynchronous, non-blocking way, and generally it should be loaded last. If you load your analytics code in a blocking way, it can negatively affect LCP.

All of the APIs used to measure the Core Web Vitals metrics were specifically designed to support asynchronous and deferred script loading (via the buffered flag), so there's no need to rush to get your scripts loaded early.

In the event that you're measuring a metric that cannot be computed later in the page load timeline, you should inline only the code that needs to run early into the <head> of your document (so it's not a render-blocking request) and defer the rest. Do not load all your analytics early just because a single metric requires it.

Do not create long tasks

Analytics code often runs in response to user input, but if your analytics code is conducting a lot of DOM measurements or using other processor-intensive APIs the analytics code itself can cause poor input responsiveness. In addition, if the JavaScript file containing your analytics code is large, executing that file can block the main thread and negatively affect a page's Interaction to Next Paint (INP).

Use non-blocking APIs

APIs like sendBeacon() and requestIdleCallback() are specifically designed for running non-critical tasks in a way that doesn't block user-critical tasks.

These APIs are great tools to use in a RUM analytics library.

In general, all analytics beacons should be sent using the sendBeacon() API (if available), and all passive analytics measurement code should be run during idle periods.

Don't track more than what you need

The browser exposes a lot of performance data, but just because the data is available does not necessarily mean you should record it and send it to your analytics servers.

For example, the Resource Timing API provides detailed timing data for every single resource loaded on your page. However, it's unlikely that all of that data will be necessarily or useful in improving resource load performance.

In short, don't just track data because it's there, ensure the data will be used before consuming resources tracking it.