Skip to Content
Course content

141: Web Vitals and Measuring Real Performance

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you've just sat down at a high-end restaurant. You're hungry. First, you're waiting for the main course to hit the table—that's the moment you actually feel like the meal has started. Then, while you're eating, imagine the waiter suddenly slides your water glass six inches to the left right as you're reaching for it. Frustrating, right? Finally, imagine you try to catch the waiter's eye to ask for a napkin, but they're staring blankly into space for five seconds before they actually acknowledge you.

That's exactly how a user experiences your website. In the world of Web Vitals, the "main course" is Largest Contentful Paint (LCP). The "sliding water glass" is Cumulative Layout Shift (CLS). And that "blank stare" from the waiter is what we call Interaction to Next Paint (INP) (which has largely replaced First Input Delay as the gold standard for responsiveness).

The "Where is my food?" problem (LCP)

LCP measures how long it takes for the largest visible element—usually a hero image or a big heading—to render on the screen. If your LCP is slow, the user thinks the site is broken or slow, even if the background scripts have already loaded. I've seen developers spend weeks optimizing a database query that saves 50ms, while the user is staring at a blank screen for 3 seconds because a massive, unoptimized 4MB JPEG is blocking the paint.

To measure this in the wild, we don't just rely on Chrome DevTools "Lighthouse" reports (which are synthetic). We use the PerformanceObserver API to see what actual users are experiencing.

const observer = new PerformanceObserver((list) => {
  const entries = list.getEntries();
  const lastEntry = entries[entries.length - 1];
  console.log(`LCP candidate: ${lastEntry.startTime}ms`);
});

observer.observe({ type: 'largest-contentful-paint', buffered: true });

Stop moving the furniture (CLS)

CLS is the "annoyance" metric. It happens when an element (like an ad or a late-loading image) suddenly pops into existence and pushes the content the user was about to click further down the page. There is nothing more infuriating than trying to click "Cancel" on a popup, only for the page to shift, and you end up clicking "Confirm Purchase" instead.

The fix is usually simple: always define width and height attributes on your images and ad containers. If you tell the browser "this image will be 300x200," the browser reserves that space before the image downloads, preventing the shift.

The responsiveness lag (INP)

While LCP is about loading, INP is about feeling. It measures the latency of every interaction a user has with your page. If you have a massive JavaScript bundle that blocks the main thread, the browser can't respond to a click or a keypress. The user clicks a button, and... nothing happens for 200ms. It feels "janky."

I usually tell my juniors that INP is the "main thread" tax. If you're running a heavy forEach loop over 10,000 items on the main thread, you're effectively telling the browser to ignore the user until that loop finishes. To fix this, we break long tasks into smaller chunks using setTimeout or requestIdleCallback.

Capturing real-world metrics in your code

Testing on your MacBook Pro with a fiber connection is a lie. Your users are on three-year-old Android phones on spotty 4G. That's why we use the web-vitals library (maintained by Google) or the native PerformanceObserver to send this data to our own analytics backend.

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

function sendToAnalytics(metric) {
  const body = JSON.stringify(metric);
  // Using navigator.sendBeacon is best for performance metrics 
  // because it doesn't block the page unload.
  navigator.sendBeacon('/analytics', body);
}

onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);

By tracking these three things, you stop guessing if your site is "fast" and start knowing exactly where the friction is.




📋 Practical Task

Implementing a Layout Shift Guard and LCP Tracker

You have a landing page where a large promotional banner loads dynamically via JavaScript, causing a massive layout shift (CLS) and delaying the Largest Contentful Paint (LCP). Your task is to:

  • Fix the CLS: Create a CSS "placeholder" or "skeleton" container for the banner with a fixed aspect ratio so the content below it doesn't jump when the image finally arrives.
  • Measure the LCP: Implement a PerformanceObserver script that listens for largest-contentful-paint and logs the final LCP value to the console once the page has fully loaded.
  • Verify: Open the Network tab in DevTools, throttle your connection to "Fast 3G," and ensure the content no longer jumps and the LCP value is accurately captured in the console.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.