Noise to Signal

◂ Blog

How to Add Web Vitals to Google Analytics and Reveal a New Search Ranking Signal

Google recently announced an upcoming shift in how website performance will affect search ranking starting in 2021. Moving forward, there will be additional emphasis on a series of metrics called the Core Web Vitals which focus on how users perceive the responsiveness of your website. These vitals address a major flaw in how website performance was measured historically. Older metrics like “page weight” described specific networking or computing resources that might be taxed, but didn’t directly describe the degree to which those bottlenecks affected users. Consequently, it was difficult to ascertain what a “good” page weight might be, beyond the conventional wisdom of smaller is better.

In this article, I’ll show you how you can incorporate core web vitals into Google Analytics. If you’re just looking for the GTM tags and triggers to accomplish this, you can download them here.

The Problem with GA’s Site Speed Metrics

Right now, Google Analytics users are stuck with the old way of measuring website performance. GA’s Page Timings section provides lots of individual metrics like ‘server response time’ and ‘page download time’ that, when combined, still tell you nothing about what the user experienced when loading a page. By far, the most damaging metric is “Average Page Load Time”. Not only is this metric poorly designed (ex: loading a large image outside of the viewport increases “load time” but wouldn’t affect the user’s experience), but using an average is a dangerous way to aggregate such a variable metric. Additionally, these metrics are collected from just 1% of users by default.

Core Web Vitals, Explained

Right now there are 3 web vitals which Google has decided matter most.

Largest Contentful Paint

LCP measures ‘perceived load speed’ by measuring the time it takes for the largest element within the user’s viewport to appear. Google suggests that 75% of your pages produce a LCP value under 2.5 seconds (segmented across mobile & desktop users).

First Input Delay

FIP measures ‘load responsiveness’ by measuring the time between the user’s first interaction and the resolution of that interaction. This metric takes aim at large JavaScript files that have gone unchecked and are blocking the browser’s main thread, thus delaying interactivity. Google suggest that 75% of your pages produce a FIP value under 100ms (segmented across mobile & desktop users).

Cumulative Layout Shift

CLS measures ‘visual stability’ by producing a calculated metric combining A) how far HTML elements shift during the user’s experience and B) the total area that shifted. In this manner, small elements that shift a lot are just as bad as large elements that shift a little. Google suggests that 75% of your pages produce a CLS score below .1 (segmented across mobile & desktop users).

The Web Vitals JavaScript Library

Sending these metrics to Google Analytics using the web-vitals JavaScript library is fairly straight forward. Note that capturing web vitals will only work on Chromium-based browsers (for the most part) as described here.

The web-vitals library contains a few key methods such as getCLS and getFID which tap into the browser’s API to retrieve the data necessary to calculate each metric. The benefit of this approach is that we can load the library at any time in order to gather our performance results. Each resulting metric value has an associated “id” which identifies the instance in which data was collected on a single page over time. You can use this ID to later generate histograms and count the number of instances in which metrics produced specific ranges of results.

Two of the core web vitals, CLS and LCP, will often report multiple values during a single page load. This makes sense when you think about it – the “largest” element loaded might change over the course of time and the “cumulative” layout shift is meant to represent the sum total of all shifts the user experienced over time. Fortunately, the web-vitals library provides a isFinal flag indicating which metric is the final value, presumably because the user has clicked a link or closed the tab. It’s for this reason that our GTM implementation takes advantage of the transport:beacon Google Analytics field to ensure our data can be sent to GA without interrupting the user’s navigation away from the page.

Update: As point out by Phil Walton at Google, the use of isFinal introduces a risk that we drop the CLS metric due to the unreliability of events attached to the unload event handler as described here. Ideally, we would capture each CLS event and then aggregate across unique users + pages to find the max CLS per user page load. This requires that you track the user’s client ID in GA, which isn’t available via the reporting API by default.

Sending Web Vitals to GA using GTM

I’ve created a GTM container export here which includes the tags, triggers, and variables necessary to send web vitals to GA as event data. Google has provided some sample code here if you’d like to send this data directly to GA without using GTM.

Download the Container

The container itself should be fairly straight forward. The only field you’ll need to edit is the “GA Property ID” variable which should be replaced with your own GA property ID.

The web vitals metrics will be sent as Google Analytics events with the following fields set:

  • Event Category: Web Vital
  • Event Action: {{Vital Name}}
  • Event Label: {{Vital ID}}
  • Event Value: {{Vital Value}}

The main code responsible for these results is as follows:

<script defer src=""></script>
  function sendVital(metric){
      //Convert CLS to integer
      metric.value = Math.round( === 'CLS' ? metric.value * 1000 : metric.value);
      dataLayer.push({"event":"Vital Reported",

First, we load the web-vitals library from a CDN. Next, we define a callback function that web-vitals will call whenever a metric is available. Within that callback function, we check to see if the metric’s value is its “final” value. We also multiply the CLS score by 1000 due to the fact that GA Event Values must be integers. Finally, we call getCLS, getFIP, and getLCP to request each of the 3 metrics.


Here’s what your data should look like if everything is working properly.

Machine generated alternative text:
Primary Dimension: Event Action Event Label Other 
Secondary dimension Sort Type: Default 
Plot Rows 
Event Action 
advanced e 
Avg. Value 
Total Events 
% of Total: 
Unique Events 
% of Total: 
Event Value 
% of Total: 
530.70% (1 ,469) 
5 (33.33%) 
Avg for View: 1,469.00 (-64.62%) 

That said, this isn’t how I would recommend reporting on your results. That’s because what we’re looking for is not the “average” value for any of these metrics, but the value below which 75% of our pages sit. Looking at the 75th percentile is a much more meaningful way of interpreting the results that is robust to outliers. We also want to segment our data by desktop & tablet vs. mobile users to ensure that our results are stable across devices.

To do this, I’ll bring these metrics into R using the googleAnalyticsR library. The code to create the visualization below can be found here. Excuse the sparsity of data! This was collected over a single day on my blog.


Web vitals present a major leap in communicating and understanding web performance and with Google’s announcement that they’ll be incorporated into ranking signals, web professionals should become more familiar with how they operate. Google has begun to incorporate these metrics into a variety of different tools such as Page Insights and Search Console which begs the question: Why take on the work of capturing this data in GA?

Here are few reasons:

  • The web vitals data in Search Console & the Chrome User Experience Report is based on anonymous users visiting popular websites. This means your website, or a specific page on your website, may not be captured in those reports.
  • The Page Insights tool provides “lab” data in the sense that its results are based on software checks. Collecting vitas from real users provides data “in the field” which can be more powerful in certain cases.
  • By collecting this data in GA, we can segment along custom dimensions such as “logged in” users, “past customers” or firmographic information such as “large companies” to understand how our site performs for our most valuable users.
  • With access to the raw metrics, we can construct more advanced analyses and graphs such as the ones shown above. This is a huge improvement over the highly summarized data provided by Search Console.

Hopefully this post gives you an opportunity to improve your website’s performance and improve your search rank.


Adam Ribaudo

Adam Ribaudo is the owner and founder of Noise to Signal LLC. He works with clients to ensure that their marketing technologies work together to provide measurable outcomes.


01. Joana Sinel

Hey Adam. I’m not familiar on how to read the data off to GA. I’ve managed to make it work and pass the events to GA but in terms of putting it in visualization – I am lost. Can you help as to how it will be visualized?

02. Matt Calik

Great article Adam! New ranking factors created some disarray for those working with websites, so great that there are pieces that explain the matter 🙂

03. Harrison Mateika

Great article, I had a smooth and easy time creating events through GTM that keep track of the core web vitals. Where I am stuff is the running the code on R Studio. I am updated to the most recent version (4.0.3) and Google is not allowing me to access the data due to a lack of client_id. I’m not very familiar with R at all and I’m a novice when it comes to OAuth and APIs so some guidance on where to add these things in your code would be greatly appreciated. Thank you!

04. Nico Forconi

Hi… I entered your code, but measure tells me “Registers an unload listener” on
Thanks, Nico.

05. Arnulfo Lacefield

You just need to push the spin button or else pull on the degree. Then the vendor will place 1 card on the table showing the worth to the desk. Just make a small wager here and see what happens.

06. Devona Brachle

It is easy to say that if you have not attempted to play the game. Be sure to view for this when taking part in the sport of poker and looking to be successful in it. Is it because they are hoping to have X dollars to do Y?

07. Kunal Gaikwad

Wonderful information,keep sharing such blogs

Curious about all-things-SEO? Come find out what the concept entails AND how it brings a treasure trove of benefits to users and businesses alike. Browse through quality content revolving around Search Engine Optimization (SEO).

Leave a Reply

Home   Blog   Portfolio   Contact  

Bringing clarity to marketers in a noisy world © 2023