Skip to content

Instantly share code, notes, and snippets.

@tunetheweb
Last active July 13, 2022 19:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tunetheweb/e11149bb7c1e25307b606fe532a8cb8d to your computer and use it in GitHub Desktop.
Save tunetheweb/e11149bb7c1e25307b606fe532a8cb8d to your computer and use it in GitHub Desktop.
JavaScript to send Web Vitals to Google Analytics with debug information
// NOTE set up a new dimension in Google Analytics and then add the dimension number on line 91
// Based on Phil Walton's post: https://web.dev/debug-web-vitals-in-the-field/
<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;
}
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 && largestEntry.sources.length) {
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: '...',
dimension1: 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>
@Kishorchandth
Copy link

Thank you so much again really appreciate it, It is working https://prnt.sc/133xmay

@tunetheweb
Copy link
Author

Actually I discovered a bug while testing this.

You should change this:

  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;
    }, null);
  }

to this:

  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;
    });
  }

i.e. remove the last , null I made you add in error.

Won't affect you at the moment as you have such perfect CLS scores but still, better to correct in case that ever changes.

@tunetheweb
Copy link
Author

I've updated the Gist again. Turns out I was missing an if statement which I've just added on line 52 (and the close on line 68). With that in place you don't need any of the ,null code that I previously advised adding.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment