Vital Web Performance

By  on  

I hate slow websites. They are annoying to use and frustrating to work on. But what does it mean to be “slow”? It used to be waiting for document load. Then waiting for page ready. But with so many asynchronous patterns in use today, how do we even define what “slow” is?

The W3C has been working on this with the new Event Timing and Element Timing API, and has defined some new Web Vital metrics to describe the different ways that slow performance can impact a webpage. Google is even going to use these web vital metrics as a search ranking factor!

Let’s have a look at them, and how we can apply them to keep our site fast and our pages well-ranked.

1. Largest Contentful Paint (LCP)

Some websites look like they loaded fast, but all the meaningful content is still waiting to be loaded. This pattern made the page load performance numbers look great, but the user still feels like the site is slow.

Largest Contentful Paint (LCP) is the time since a page was started that the largest block of meaningful content was loaded. Largest is measured in the pixel dimension of the element, so it could be anything that takes up a lot of space in your UI. This could be a big image, block of text, or a really annoying advertisement.

Web pages that only show the UI “frame” in the main document and load the content asynchronously will have poor LCP scores. Fun fact, we recently audited the web performance of Google search and they inline nearly all the content in the original document!

Learn more about the Largest Contentful Paint

2. Cumulative Layout Shift (CLS)

Janky web pages that keep shifting around, drawing new content, and pushing down the things you were trying to read, have lots of Layout Shift. Layout Shift happens whenever new elements added to the page move the placement of other elements. Like an advertisement rendering on top of that paragraph you wanted to read should have been. Looking at you The New York Times.

Cumulative Layout Shift (CLS) is the sum of all the layout shifts that happen on a page. When there is a lot of asynchronous content, there are lots of layout shifts and the CLS is high.

This generally happens when large portions of a webpage’s content are loaded asynchronously into the document by an AJAX call and client-side rendering. Third-party advertising is the classic offender.

Learn more about the Cumulative Layout Shift.

3. First Input Delay (FID)

Web pages that load obnoxious amounts of JavaScript, tracking pixels, and asset dependencies are asking the browser to do a lot. Each of these assets has to be discovered, downloaded, parsed, and applied—and that’s a lot of work! If the browser is busy doing this work when the user first tries to use your page, it feels slow.

First Input Delay (FID) is how long the page is busy when the user tries to interact with the page for the first time. This isn’t a measure of event handler code, it’s the time the browser delays handling the event because it’s busy. If the browser is busy parsing lots of JavaScript when the user tries to click a button, then there is a large FID.

Developers often solve this problem with a loading screen, which delays the first input rather than solving the problem: loading too many things!

Learn more about the First Input Delay

Measuring Your Web Vitals

Now that we know the concept behind these vital metrics, how do we measure them? They are all based on the draft spec for Element Timing API, which is not yet well adopted. Chrome (and other Blink-based browsers) support this today, so you can start capturing these metrics for some of your users.

try {
    new PerformanceObserver(entryList => {
    }).observe({ type: "layout-shift", buffered: true });

    new PerformanceObserver(entryList => {
    }).observe({ type: "largest-contentful-paint", buffered: true });

    new PerformanceObserver(entryList => {
    }).observe({ type: "first-input", buffered: true });
catch(e) { /* API is not supported */ }

Measuring each of these types has its own gotchas and special conditions. For example, to handle ”layout-shift”, we need to sum every value that we receive because we are measuring the cumulative layout shift. We should also consider whether the layout shift was caused by a user input, which is one of the custom properties attached to this entry.

let cumulativeLayoutShift = 0;
function handleLayoutShift(entry) {
  if (!entry.hadRecentInput) {
    cumulativeLayoutShift += entry.value;

The links above cover the various implementations and requirements for each metric. You’ll need to decide how you want to capture and save these metrics, as well as reporting on them.

Or Request Metrics can do it for you! Request Metrics is the fastest, simplest, and cheapest way to understand real user web performance. It can capture these metrics, along with a bunch of other useful data, and distill it down to what’s really important. All for just $10/mo.

It’s way easier than chasing these moving APIs yourself.

Let’s go fast.

Todd Gardner

About Todd Gardner

Todd H Gardner is the president and co-founder of TrackJS, an error monitoring service for modern JavaScript applications. With over a decade of experience building web applications for large enterprises and startups, Todd has seen how the web fails.

Recent Features

  • By
    fetch API

    One of the worst kept secrets about AJAX on the web is that the underlying API for it, XMLHttpRequest, wasn't really made for what we've been using it for.  We've done well to create elegant APIs around XHR but we know we can do better.  Our effort to...

  • By
    Serving Fonts from CDN

    For maximum performance, we all know we must put our assets on CDN (another domain).  Along with those assets are custom web fonts.  Unfortunately custom web fonts via CDN (or any cross-domain font request) don't work in Firefox or Internet Explorer (correctly so, by spec) though...

Incredible Demos


    Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!