How to Fix Cumulative Layout Shift on Your Website

What Is Cumulative Layout Shift and Why Does It Matter?

You have probably experienced it yourself. You are reading an article, about to tap a link, and suddenly the entire page jumps. A banner loads late, an image pops in without warning, or a font swap reshuffles every line of text. That frustrating experience is exactly what Cumulative Layout Shift (CLS) measures.

CLS is one of Google’s Core Web Vitals, and it directly impacts your search rankings, user experience, and conversion rates. A poor CLS score means visitors are more likely to misclick, feel disoriented, and leave your site.

In this guide, we will walk you through how to fix Cumulative Layout Shift using practical debugging steps, Chrome DevTools, and real code examples you can apply today.

What Is a Good CLS Score?

CLS Score Range Rating What It Means
0 – 0.1 Good Minimal or no unexpected layout shifts
0.1 – 0.25 Needs Improvement Noticeable shifts that may frustrate users
Above 0.25 Poor Significant layout instability, hurts SEO and UX

Your goal should be to keep CLS under 0.1 for at least 75% of page loads.

Step 1: Identify Your CLS Problems with Chrome DevTools

Before you fix anything, you need to know exactly what is shifting and why. Chrome DevTools gives you everything you need.

Method A: Performance Panel Recording

  1. Open Chrome DevTools (F12 or right-click > Inspect).
  2. Go to the Performance tab.
  3. Click the record button, then reload the page.
  4. Stop the recording after the page fully loads.
  5. Look for the “Layout Shift” entries in the Experience row. Each red or orange block represents a layout shift event.
  6. Click on a Layout Shift entry to see which DOM elements moved, their previous position, and their new position.

Method B: The Layout Shift Regions Flag

  1. In DevTools, press Ctrl+Shift+P (Cmd+Shift+P on Mac) to open the Command Menu.
  2. Type “Show Layout Shift Regions” and enable it.
  3. Reload your page. Blue rectangles will flash on screen wherever layout shifts occur.

This visual overlay makes it immediately obvious which elements are causing trouble.

Method C: Lighthouse and PageSpeed Insights

Run a Lighthouse audit directly in DevTools (Lighthouse tab) or use Google PageSpeed Insights. Both tools will flag CLS issues and point you to the specific elements responsible.

Step 2: Reserve Space for Images and Media

Images and videos loading without predefined dimensions are one of the most common causes of CLS. When the browser does not know the size of an image before it loads, it allocates zero space. Once the image downloads, everything below it gets pushed down.

The Fix: Always Set Width and Height

Add explicit width and height attributes to every <img> and <video> tag:

<img
  src="/images/hero-banner.webp"
  alt="Product showcase"
  width="1200"
  height="630"
  loading="lazy"
/>

Modern browsers use these attributes to calculate the aspect ratio before the image loads, reserving the correct amount of space automatically.

Use CSS aspect-ratio for Responsive Layouts

If you use responsive images that scale to their container, combine the HTML attributes with CSS:

img {
  max-width: 100%;
  height: auto;
  aspect-ratio: attr(width) / attr(height);
}

Or set the aspect ratio directly:

.hero-image {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

This ensures images never cause layout shifts, regardless of screen size.

Handle Iframes and Embeds

Third-party embeds like YouTube videos, maps, or social media widgets are notorious for CLS. Wrap them in a container with a fixed aspect ratio:

.video-wrapper {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
}

.video-wrapper iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

Step 3: Stabilize Web Font Loading

When web fonts load late, the browser swaps from a fallback font to the custom font. This can change letter spacing, line height, and word wrapping, causing significant layout shifts across your entire page.

Fix 1: Use font-display: optional or swap

@font-face {
  font-family: 'CustomSans';
  src: url('/fonts/custom-sans.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: optional;
}
  • font-display: optional prevents layout shifts entirely. If the font is not available immediately, the fallback is used for the entire page lifecycle. Best for CLS.
  • font-display: swap shows the fallback first and then swaps. This can still cause shifts but ensures your custom font always appears eventually.

Fix 2: Preload Critical Fonts

Tell the browser to start downloading your fonts as early as possible:

<link
  rel="preload"
  href="/fonts/custom-sans.woff2"
  as="font"
  type="font/woff2"
  crossorigin
/>

Fix 3: Match Fallback Font Metrics

Use the CSS size-adjust, ascent-override, descent-override, and line-gap-override properties to make your fallback font match your custom font as closely as possible:

@font-face {
  font-family: 'CustomSans-Fallback';
  src: local('Arial');
  size-adjust: 105%;
  ascent-override: 92%;
  descent-override: 22%;
  line-gap-override: 0%;
}

Then reference both fonts in your stack:

body {
  font-family: 'CustomSans', 'CustomSans-Fallback', Arial, sans-serif;
}

This technique dramatically reduces the visible shift when the custom font finally loads.

Step 4: Handle Dynamic Content Injection

Content that gets injected after the initial render (ads, cookie banners, notification bars, lazy-loaded components) is a major CLS offender. Here is how to fix each scenario.

Ads and Banners

  • Reserve a fixed-size container for every ad slot before the ad loads.
  • Use a min-height on the ad container that matches the expected ad dimensions.
  • If the ad does not fill, display a subtle placeholder or collapse the space gracefully after you know the ad will not load.
.ad-slot-leaderboard {
  min-height: 250px;
  width: 100%;
  background-color: #f5f5f5;
}

Cookie Consent Banners

Cookie banners that push content down are a common CLS problem. The best approach is to overlay them instead of inserting them into the document flow:

.cookie-banner {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 9999;
}

Using position: fixed means the banner floats above the content and never causes a layout shift.

Lazy-Loaded Components and Infinite Scroll

  • Always set a min-height or use a skeleton placeholder for content that loads below the fold.
  • Avoid inserting content above or in the middle of what the user is currently viewing.
  • For infinite scroll, append new items at the bottom only.

Notification Bars and Top Banners

If you must show a promotional or notification bar at the top of the page:

  • Include it in the initial HTML rather than injecting it with JavaScript.
  • If it must be dynamic, use transform animations instead of changing height or margin, because transforms do not trigger layout recalculations.
.promo-bar {
  transform: translateY(-100%);
  transition: transform 0.3s ease;
}

.promo-bar.visible {
  transform: translateY(0);
}

Step 5: Optimize CSS to Prevent Shifts

Poorly structured CSS can cause layout instability in subtle ways.

Common CSS Fixes

Problem Fix
Content reflows after stylesheet loads Inline critical CSS in the <head>
Elements resize on viewport change Use relative units with min-height constraints
Late-loading CSS overrides cause shifts Preload critical stylesheets with <link rel="preload">
Animations change layout properties Use transform and opacity instead of width, height, top, or margin

Inline Critical CSS Example

Extract the CSS needed for above-the-fold content and place it directly in your HTML:

<head>
  <style>
    .header { height: 64px; }
    .hero { aspect-ratio: 16/9; width: 100%; }
    .nav { min-height: 48px; }
  </style>
  <link rel="preload" href="/css/main.css" as="style" />
  <link rel="stylesheet" href="/css/main.css" />
</head>

Step 6: Fix CLS for Server-Side Rendered and Express.js Applications

If you are building applications with Express.js, you have a unique advantage: you can control exactly what HTML is sent to the browser on the first response, eliminating many sources of CLS before the page even reaches the client.

Best Practices for Express.js Apps

  1. Include all critical UI elements in the initial server response. Render headers, navigation bars, and hero sections server-side instead of relying on client-side JavaScript to inject them.
  2. Inline critical CSS in your template. Use your Express template engine (EJS, Pug, Handlebars) to include above-the-fold styles directly in the <head>.
  3. Set image dimensions in your templates. Store image width and height metadata alongside your content data and output them in every <img> tag.
  4. Serve font files from your own domain with proper cache headers to ensure fast delivery and reduce font-swap shifts.
// Example Express.js route that includes image dimensions
app.get('/blog/:slug', async (req, res) => {
  const post = await getPost(req.params.slug);
  // post.heroWidth and post.heroHeight stored in database
  res.render('blog-post', {
    title: post.title,
    heroImage: post.heroImage,
    heroWidth: post.heroWidth,
    heroHeight: post.heroHeight,
    content: post.content
  });
});
<!-- In your EJS template -->
<img
  src="<%= heroImage %>"
  width="<%= heroWidth %>"
  height="<%= heroHeight %>"
  alt="<%= title %>"
  loading="eager"
/>

Step 7: Verify Your Fixes

After applying changes, you need to verify that your CLS score has actually improved. Use these tools:

  • Google PageSpeed Insights for both lab data and field data (from real users via the Chrome User Experience Report).
  • Chrome DevTools Lighthouse for quick local testing during development.
  • Google Search Console (Core Web Vitals report) for tracking improvements across all your pages over time.
  • Web Vitals JavaScript library for monitoring CLS in production with real user metrics.

Add Real User Monitoring

Install the web-vitals library and log CLS data from actual visitors:

import { onCLS } from 'web-vitals';

onCLS((metric) => {
  console.log('CLS:', metric.value, metric.entries);
  // Send to your analytics endpoint
  fetch('/api/analytics', {
    method: 'POST',
    body: JSON.stringify({
      name: 'CLS',
      value: metric.value,
      page: window.location.pathname
    }),
    headers: { 'Content-Type': 'application/json' }
  });
});

This gives you real-world data, which is ultimately what Google uses for ranking.

Quick CLS Fix Checklist

Use this checklist as a reference whenever you audit a page:

  • ☑ All <img> and <video> elements have width and height attributes
  • ☑ Responsive images use aspect-ratio in CSS
  • ☑ Web fonts use font-display: optional or are preloaded
  • ☑ Fallback font metrics are adjusted to match custom fonts
  • ☑ Ad slots have reserved space with min-height
  • ☑ Cookie banners use position: fixed or position: sticky
  • ☑ No content is injected above the current viewport
  • ☑ Critical CSS is inlined in the <head>
  • ☑ Animations use transform instead of layout-triggering properties
  • ☑ Third-party embeds are wrapped in aspect-ratio containers

Frequently Asked Questions

How do I solve cumulative layout shift?

Start by identifying which elements are shifting using Chrome DevTools Performance panel or the Layout Shift Regions overlay. Then apply the appropriate fix: set explicit dimensions for images and embeds, stabilize font loading with font-display and preloading, reserve space for ads and dynamic content, and inline critical CSS. Test your changes with Lighthouse or PageSpeed Insights to confirm improvement.

What is a good cumulative layout shift score?

Google considers a CLS score of 0.1 or less as good. Scores between 0.1 and 0.25 need improvement, and anything above 0.25 is rated poor. Aim for 0.1 or lower across at least 75% of your page loads.

Why is CLS bad for my website?

CLS creates a poor user experience because unexpected page movement causes users to click the wrong buttons, lose their reading position, and feel frustrated. Beyond UX, CLS is a Google ranking factor as part of Core Web Vitals. A poor CLS score can directly hurt your organic search visibility.

How do I fix cumulative layout shift caused by images?

Always include width and height attributes on your <img> tags. For responsive designs, combine these attributes with max-width: 100% and height: auto in your CSS. You can also use the CSS aspect-ratio property to reserve space before the image loads.

How do I fix CLS caused by web fonts?

Use font-display: optional in your @font-face declarations to prevent layout shifts entirely. Preload your font files with <link rel="preload"> so they arrive faster. Additionally, adjust your fallback font metrics using size-adjust and ascent-override to minimize the visual difference during font swap.

Can dynamic content like ads cause CLS?

Yes, ads and other dynamically injected content are among the most common causes of CLS. The fix is to always reserve space for ad slots by setting a min-height on the container element before the ad script runs. For overlays like cookie banners, use position: fixed so they do not push page content around.

How long does it take for Google to recognize CLS improvements?

Google’s Core Web Vitals data in Search Console is based on a 28-day rolling average from real users in the Chrome User Experience Report. After deploying your fixes, expect it to take approximately 28 days for the improvements to fully reflect in your CWV scores and potentially influence rankings.

Recent Posts

No Posts Found!

Categories

Tags

    Subscribe

    You have been successfully Subscribed! Ops! Something went wrong, please try again.

    About Us

    Express Jam Studio was founded in 2004 by John Smith. John had previously worked for a courier company, but he saw an opportunity to start his own business in the web design and development industry.

    Contact Info

    Copyright © 2022 Express Jam Studio. All Rights Reserved.