Technical SEO: Core Web Vitals and Performance

Technical SEO: Core Web Vitals and Performance

Core Web Vitals are a set of real-world metrics that Google uses to measure user experience on the web. They directly impact search rankings, so optimizing them is essential for any website. The three metrics are Largest Contentful Paint (LCP), First Input Delay (FID), and Cumulative Layout Shift (CLS). This article explains each metric in detail and shows you how to optimize for them.

Largest Contentful Paint (LCP)

LCP measures how long it takes for the largest visible element (usually a hero image, heading, or video) to render on screen. Google’s threshold is 2.5 seconds. A slow LCP makes a site feel sluggish and increases bounce rates. The most common causes of slow LCP are render-blocking resources (CSS, JavaScript), unoptimized images, and slow server response times. To improve LCP, preload your hero image so the browser discovers it early, use responsive image sizes with srcset, serve images in modern formats like WebP or AVIF, and minimize CSS and JavaScript that block the critical rendering path. Server-side improvements like using a CDN and enabling HTTP/2 can also cut LCP significantly.

<!-- Preload the hero image for faster LCP -->
<link rel="preload" href="hero.webp" as="image">

<!-- Responsive images with modern format -->
<img src="hero.webp"
     srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"
     sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
     width="1200" height="600"
     loading="lazy" decoding="async"
     alt="Hero image showcasing the product">

The loading="lazy" attribute defers loading of off-screen images, but the hero image should not use lazy loading — it should load eagerly since it is the largest element. The decoding="async" attribute allows the browser to decode the image off the main thread. Always set explicit width and height attributes to prevent layout shifts as images load, and also to improve CLS. The srcset attribute with sizes tells the browser which image size to download based on the viewport width, saving bandwidth on mobile devices while delivering sharp images on retina displays.

First Input Delay (FID)

FID measures the time between when a user first interacts with your site (clicking a button, tapping a link) and when the browser can actually respond to that interaction. Google’s threshold is 100 milliseconds. FID is primarily affected by heavy JavaScript execution on the main thread. If the browser is busy parsing, compiling, or executing a large script, user interactions will lag. To reduce FID, break up long JavaScript tasks (over 50 ms) using techniques like code splitting, deferring non-critical scripts with defer or async, and lazy-loading third-party scripts. Web workers can also move heavy computation off the main thread entirely.

<!-- Defer non-critical JavaScript -->
<script src="analytics.js" defer></script>

<!-- Code splitting with dynamic imports (JavaScript) -->
button.addEventListener('click', async () => {
    const { showModal } = await import('./modal.js');
    showModal();
});

The defer attribute ensures the script executes after the HTML is fully parsed, in document order, but before the DOMContentLoaded event. This prevents render-blocking while still preserving execution order. Dynamic import() splits your bundle so that heavy components (modals, charts, editors) are only loaded when the user actually needs them rather than on initial page load.

Cumulative Layout Shift (CLS)

CLS measures visual stability by tracking unexpected layout shifts during the page’s lifetime. Google’s threshold is a score of 0.1. A layout shift occurs when a visible element changes position between two frames — for example, when an image loads without dimensions and pushes content down, or when a late-loading ad banner inserts itself at the top of the page. Each shift is scored based on the fraction of the viewport that moved and the distance moved. To keep CLS low, always set explicit dimensions on images, videos, and iframes. Reserve space for dynamic content like ads or embeds using placeholder containers with a fixed aspect ratio. Avoid inserting content above existing content unless it is in response to a user interaction.

<!-- Reserve space for a dynamic ad slot -->
<div id="ad-slot" style="width: 300px; height: 250px;"></div>

<!-- Aspect ratio container for an embedded video -->
<div style="aspect-ratio: 16/9; max-width: 560px;">
    <iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ"
            width="560" height="315"
            style="width: 100%; height: 100%;"></iframe>
</div>

The CSS aspect-ratio property is a modern, clean way to reserve space for embeds and responsive images without using the old padding-bottom hack. Browsers with support for aspect-ratio will automatically calculate the height based on the width, preventing layout shifts as the content loads.

Measuring Core Web Vitals

Google provides several tools for measuring Core Web Vitals. The Chrome User Experience Report (CrUX) gives real-user data aggregated by Google. Lighthouse provides a lab-based audit with specific recommendations. PageSpeed Insights combines both real-user and lab data. For field data, use the web-vitals JavaScript library to capture metrics from your actual users and send them to your analytics platform. Aim for the 75th percentile of your users to pass Google’s thresholds — that means 75% of your users should experience LCP under 2.5 seconds, FID under 100 ms, and CLS under 0.1.

// Track real-user Core Web Vitals
import { onLCP, onFID, onCLS } from 'web-vitals';

function sendToAnalytics(metric) {
    navigator.sendBeacon('/analytics', JSON.stringify(metric));
}

onLCP(sendToAnalytics);
onFID(sendToAnalytics);
onCLS(sendToAnalytics);

Core Web Vitals optimization is not a one-time task — monitor your metrics regularly and set up alerts for regressions, especially after deploying new features or third-party scripts. A performance budget in your CI pipeline can prevent regressions before they reach production.

Leave a Reply

Your email address will not be published. Required fields are marked *