Skip to Main Content

Monitor & Set-up Core web vitals report with Web-vitals JavaScript

Core Web Vitals focuses on three aspects of User Experience - Loading performance (LCP), Interactivity (FID) and visual stability (CLS).

The web-vitals library is a tiny (~1K), modular library for measuring all the Web 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, Page Speed Insights, Search Console's Speed Report).
These web-vitals JS help you generate Report and display in a histogram and timeline of each Web Vitals metric.

monitor Core web vitals with web-vitals js

In this step-by-step tutorial, I am going to show you how to set up & monitor Core Web vitals with web-vitals js libraries on our project with the help of Elementor pro: Custom Code functionalities & Code Snippet plugin.

The web-vitals library is a tiny (~1K), modular library for measuring all the Web 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, Page Speed Insights, Search Console’s Speed Report).

I will be focusing mostly on improving the Cummulative Layout shift (CLS) and the Largest Contentful Paint (LCP) problems in my article.

Core web vitals report

If you’re fixing your Core Web Vitals issues, you need to first learn what is field data and lab data.

Lab-data is synthetically collected in a testing environment, is critical for tracking down bugs and diagnosing issues because it is reproducible and has an immediate feedback loop like lazy loading images fixes the “defer offscreen images”.

Field-data allows you to understand what real-world users are experiencing, conditions that are hard to simulate in the lab like latency, different devices, cache condition, etc.

If you want to know why your Lab & field data are different? Here is the answer from the web.dev.

This is the test I run on GTMetrix on the specific network conditions.

Testing Environments

Native speed: No throttling

Test Running in no data cap speed connection using Chrome Browser and the LCP is 700ms
WebPageTest Running in LTE speed connection using Chrome Browser and the LCP is 1sec

We can see the decrease in LCP performance from 364ms to 800ms this shows how your real users are experiencing when they visit your website from slow connections and this is the Field data/ RUM Data I collected with Web-vitals JS libraries in 2 days.

Data collected by web-vitals JS is totally different from Lighthouse score

As you can see from the screenshot above (lighthouse and Field report) both are different from each other.

You can see the data, it is mismatched. This is mainly due to Inconsistent in CDN performance, my server response time (TTFB), visitor’s connections, location, etc.

These types of metrics are out of my control.

To start monitoring Core Web Vitals performance on ourselves, you need to have:

  1. Google Analytics such as Universal Analytics & GA4.
  2. Elementor Pro/ Code snippets plugin/ a Child theme.

To do that-

1) Universal Analytics users

This tutorial is meant for “Universal Analytics” users only not for the GA4/ GTM + Analytics users.

To start collecting data, we have to set up a Custom Dimension in Google Analytics.

To do that, follow the step-by-step guide-

  1. Login to your Google Analytics Property.
  2. Click the Admin, bottom left corner (see the screenshot below)
  3. Google Analytics Admin area
  4. Under Property Tab - scroll down and find Custom Dimensions (see screenshot for more details)
  5. Custom Dimension in Google Analytics
  6. Click the New Dimensions and add a new dimension and set it to active
  7. Remember the Index Number, it is important. When we define Dimension number we will use index number whether it is 1 or 2.
  8. Index number
So let’s set up Web vitals JS libraries with Elementor pro and Code snippet plugins and start monitoring Core Web Vitals Performance.

Setting up Web Vitals JS libraries

To monitor Core Web Vitals, we have to set up web vitals JS libraries and send the events to Google Analytics.

To set up, we have to use 3rd parties plugins like Elementor Pro: Custom Code, Code Snippet, etc.

#1. Elementor Pro

If you’re using Elementor Pro, you can use Custom Code functionalities to set up web-vitals JS without touching even a line of PHP code.

Step#1(A): Custom Code

Elementor's Custom Code

To Access Custom Code, make sure you have Elementor Pro to version 3.1.0 and above.

  • Go to the Backend of your WordPress website.
  • Hover your mouse on the Elementor.
  • Click the Custom Code.

Custom Code is a tool that gives you one place where you can insert styles and scripts like adding Google Analytics Tracking code, metatag etc

Click the Add New button: The second step is where we add web-vitals js libraries.

When you add the code, you have to define the location where the Custom Code will appear and give priority in which order the custom code will appear.

Add code snippets to your page without writing any php code with Elementor pro custom code

So when your visitors visit your website, web-vitals JS will automatically collect the data and send the web vitals events to Google Analytics. See the screenshot below
Web Vitals Events sent by Web vitals JS libraries

Step#1(B): Add the web vitals JS code

For some reason, if the code provided is not working properly. Head over to Smashing Magazine JS to get the correct syntax.

Web-vitals JS with Elementor pro - Custom Code
				
					  <!--You can host web vitals JS locally on your server-->
  
     <script type="module">
  // Load the web-vitals library from unpkg.com (or host locally):
  import {getFCP, getLCP, getCLS, getTTFB, getFID} from 'https://unpkg.com/web-vitals?module';
  
  function getSelector(node, maxLen = 100) {
    let sel = '';
    try {
      while (node && node.nodeType !== 9) {
        const part = node.id ? '#' + node.id : node.nodeName.toLowerCase() + (
          (node.className && node.className.length) ?
          '.' + Array.from(node.classList.values()).join('.') : '');
        if (sel.length + part.length > maxLen - 1) return sel || part;
        sel = sel ? part + '>' + sel : part;
        if (node.id) break;
        node = node.parentNode;
      }
    } catch (err) {
      // Do nothing...
    }
    return sel;
  }
  
  function getLargestLayoutShiftEntry(entries) {
    return entries.reduce((a, b) => a && a.value > b.value ? a : b);
  }
  
  function getLargestLayoutShiftSource(sources) {
    return sources.reduce((a, b) => {
      return a.node && a.previousRect.width * a.previousRect.height >
          b.previousRect.width * b.previousRect.height ? a : b;
    });
  }
  
  function wasFIDBeforeDCL(fidEntry) {
    const navEntry = performance.getEntriesByType('navigation')[0];
    return navEntry && fidEntry.startTime < navEntry.domContentLoadedEventStart;
  }
//   After we collected web vitals data from real users, we have to send as events to Google Analytics  
  
  function sendWebVitals() {
    function sendWebVitalsGAEvents({name, delta, id, entries}) {
      if ("function" == typeof ga) {
  
        let webVitalInfo = '(not set)';
  
        // Set a custom dimension for more info for any CVW breaches
        // In some cases there won't be any entries (e.g. if CLS is 0,
        // or for LCP after a bfcache restore), so we have to check first.
        if (entries.length) {
          if (name === 'LCP') {
             const lastEntry = entries[entries.length - 1];
             webVitalInfo =  getSelector(lastEntry.element);
          } else if (name === 'FID') {
            const firstEntry = entries[0];
            webVitalInfo = getSelector(firstEntry.target);
          } else if (name === 'CLS') {
             const largestEntry = getLargestLayoutShiftEntry(entries);
             if (largestEntry && largestEntry.sources) {
                const largestSource = getLargestLayoutShiftSource(largestEntry.sources);
                if (largestSource) {
                  webVitalInfo = getSelector(largestSource.node);
                }
             }
          }
        }
  
        ga('send', 'event', {
          eventCategory: 'Web Vitals',
          eventAction: name,
          // The `id` value will be unique to the current page load. When sending
          // multiple values from the same page (e.g. for CLS), Google Analytics can
          // compute a total by grouping on this ID (note: requires `eventLabel` to
          // be a dimension in your report).
          eventLabel: id,
          // Google Analytics metrics must be integers, so the value is rounded.
          // For CLS the value is first multiplied by 1000 for greater precision
          // (note: increase the multiplier for greater precision if needed).
          eventValue: Math.round(name === 'CLS' ? delta * 1000 : delta),
          // Use a non-interaction event to avoid affecting bounce rate.
          nonInteraction: true,
          // Use `sendBeacon()` if the browser supports it.
          transport: 'beacon',
  
          // OPTIONAL: any additional params or debug info here.
          // See: https://web.dev/debug-web-vitals-in-the-field/
          // dimension1: '...',
          // dimension2: '...',
          
          dimension2: webVitalInfo
          // ...
        });
      }
    }

    // Register function to send Core Web Vitals and other metrics as they become available
    getFCP(sendWebVitalsGAEvents);
    getLCP(sendWebVitalsGAEvents);
    getCLS(sendWebVitalsGAEvents);
    getTTFB(sendWebVitalsGAEvents);
    getFID(sendWebVitalsGAEvents);
  
  }
  
  sendWebVitals();
</script>
				
			

But If you’re not using Elementor pro, there are plugins you can use to implement web-vitals.js libraries. ( These are the plugin that comes up in my mind while I was writing)

  1. Header and Footer Scripts by Digital Liberation.
  2. Head and Footer Scripts Inserter By Space X-Chimp.
  3. Code Snippets By Code Snippets Pro (highly recommend)

#2. Code Snippets Plugins:

  1. Step#2(A): Install the plugin: If you don’t have Elementor pro, You can use the Code snippets plugin from WP Repository or upload it using an FTP client.
Download The Code snippets plugin from WP Repository
  1. Step#2(B): Click the Add new:
Add new Snippet

Step#2(C): Deploy the new snippet with PHP

Instead of inline the JS to the head or body open, I combine the JS into a script tag.

Web vitals implementation with PHP
				
					<!--First We enqueue the script and then load the JS in JS module-->

function web_vitals_int(){
    wp_enqueue_script('web-vitals','/wp-content/assets/js/web-vitals-int.min.js', false, '1.01', 'all');
}
add_action('wp_enqueue_scripts','web_vitals_int');


/*
* We Add the Module to web-vitals js libraries
*/
function add_type_attribute_web_vitals($tag, $handle, $src) {
    
    // if not your script, do nothing and return original $tag
    if ( 'web-vitals' !== $handle ) {
        return $tag;
    }
    // change the script tag by adding type="module" and return it.
    $tag = '<script type="module" defer src="' . esc_url( $src ) . '"></script>';
    
    return $tag;
}
add_filter('script_loader_tag', 'add_type_attribute_web_vitals' , 10, 3);

				
			

Now, if you try to see the score in UA with the help of a custom Dashboard, you will not see the whole picture. That is why we need to use web vitals reports.

Core Web Vitals events with Custom Events as secondary dimension

Web Vitals Report 

After you set up web vitals JS libraries, give some time to collect data from real users. So you can analyze-

  1. Head over to Web Vitals Report
Web Vitals Report
  1. Login to your Google Analytics Account– Now you have to log in to your Google Analytics account to retrieve data. For privacy reasons, I have blurred my email address.
After you click the login button, it show pop up and ask your credentials
  1. Click the check box to Use advanced options (configurable per account) and add debug information:
    a) choose your Google Analytics property.
    b) choose the date range and compare segments
    c) Add
    ga:dimension1 to debug information input fill
    d) Click the submit button. 
ga:dim

These are the results from top countries and Pages from the past 7 days (your data might differ from mine).

Results-

Before the optimization:

Data retrieve from Google Analytics for top countries and pages
Top pages in web Vitals report

Now you can see my Core Web Vitals score (above which I collected from real-users) which is bad and need immediate action.

After optimizations, I did in a few days and these are the results.

After the optimization:

ga:dim

These are the results from top countries and Pages from the past 4 months.

Web vitals Report showing which countries has best and worst Core Web vitals score
After Optimization CWV page

2) Google Tag Manager + Universal Analytics

If you’re using Google Tag Manager + Universal Analytics, you can also monitor Core Web Vitals with the help of the tutorials.

3) Google Tag Manager + Google Analytics 4 (GA4)

If you’re using Google Tag Manager with GA4 or Google Analytics 4, then I highly recommend using the Simo post on how to Track Core Web Vitals in GA4 with Google Tag Manager or Follow web.dev article on measure and debug performance with GA 4 with BigQuery

4) Google Analytics 4 (GA4) 

if you’re a GA4 user, I highly recommend reading the bigcommerce website advantage article on Core Web Vitals Tracking with a GA4 article.

If you see the before & after the optimization. We can see the significant improvement in Core Web Vitals performance thanks to the Web Vitals JS libraries team, Barry Pollard’s article on Smashing Magazine, and Philip Walton’s article on the web.dev.

Now I pass the Core Web Vitals Assessments for more than 5 months and still counting.

Pass Core Web Vitals Assessment
Page Experience Report showing all the page pass Core Web Vitals

Here is the continuation of what you can do with the debug information we send to Google Analytics.

You can see it is difficult whether is a good idea to preload hero image or not. Follow my tutorials to learn more and decide it for yourself.