How to Fix Cumulative Layout Shift in WordPress

TL;DR: CLS problems in WordPress stem from five key causes: images without dimensions, font swaps, late-loading ads and embeds, JavaScript-injected content like cookie banners and sliders, and misconfigured optimization plugins. Diagnose with Search Console, PageSpeed Insights, and Chrome DevTools—then fix by category. If your field score doesn’t budge right away, wait: real-user data takes up to four weeks to reflect changes. For automation, NitroPack’s free plan covers image sizing and critical CSS; pair it with a dedicated ad plugin if you run AdSense.

Cumulative Layout Shift (CLS) measures how much your page’s content moves around unexpectedly while someone is using it—buttons jumping mid-click, paragraphs sliding out from under your eyes, a banner shoving everything down half a second after the page loads. It’s one of Google’s three Core Web Vitals, and it comes with clear thresholds: a score of 0.1 or lower is good, 0.1–0.25 needs improvement, and anything above 0.25 is poor.

If you’re here, you’ve probably seen a CLS warning in Search Console or watched your PageSpeed score refuse to budge—maybe even after you’ve already tried a fix or two.

We’re going to walk through: 

  1. Find exactly what’s shifting and why.
  2. Fix each type by category.
  3. Automate the fixes with a plugin if you’d rather not do it all manually.

How to diagnose CLS on your WordPress site

Before you fix anything, you need to know exactly what’s shifting and why. A lot of CLS troubleshooting goes sideways because people skip straight to solutions without pinpointing the source—and end up fixing the wrong thing. Here’s a three-tool workflow that narrows it down fast.

1. Start with the big picture in Search Console

Open Google Search Console → Experience → Core Web Vitals and check the Mobile report first (mobile CLS is almost always worse—more on that later). Look for any rows flagged as CLS issues, export the affected URLs, and group them by template type: homepage, blog posts, category archives, product pages, and so on.

Search Console won’t tell you what shifted on a specific page, but it’s great for spotting patterns. If every product page is failing but your blog posts are fine, that tells you exactly where to look next.

2. Zoom in with PageSpeed Insights

Pick one representative URL from each failing group and run it through PageSpeed Insights. Here’s where it gets important: look at the field data section first (the real-user numbers at the top), not the Lighthouse lab score at the bottom. The field data comes from the Chrome UX Report and reflects what actual visitors experience at the 75th percentile—that’s the number Google uses for ranking.

Example of Field Data information in a Core Web Vitals Assessment

The lab results underneath can serve more as clues, rather than verdicts. They’re useful for identifying which elements shifted, but they don’t represent your real-world score.

3. Reproduce and inspect in Chrome DevTools

This is where you find the actual culprit. Open the failing page in Chrome, go to DevTools → Performance panel, and hit Record. Load the page, then scroll, trigger any popups, and interact with consent banners—anything a real visitor would do.

Once you stop recording, look for the Layout Shifts track in the timeline. Click on the largest shift cluster to see which element moved, and this will show you who is the more likely victim. If a paragraph jumped down, look above it—an image loading without dimensions, a late-injecting ad slot, or a font swap is probably the trigger.

Common WordPress culprits you’ll find this way:

  • Images or iframes missing width and height attributes.
  • Ads, embeds, or widgets that load after the initial paint.
  • Cookie consent banners that push content down instead of overlaying it.
  • Sliders or carousels with unstable container heights.
  • Custom fonts swapping in with different metrics than the fallback.

If you can’t reproduce the shift in DevTools, try isolating it on a staging site—disable plugins one by one (especially ad, popup, and optimization plugins), switch to a default theme, and test while logged out.

Still can’t find it? Add real-user monitoring with theweb-vitals JavaScript library’s attribution build. It reports diagnostic fields like largestShiftTarget, largestShiftTime, largestShiftValue, and loadState—so you can see exactly what shifted, when, and during which phase of the page load.

Why your last fix might have failed

This catches more people than you’d expect. You apply a fix, re-run PageSpeed Insights, and the score hasn’t changed. So you assume the fix failed.

It probably didn’t.

The issue is that field data uses a 28-day rolling window from real Chrome users, while lab data updates immediately. Your fix might be working perfectly—but the field score won’t fully reflect it for up to four weeks.

The rule of thumb: lab data confirms the cause, field data confirms users actually improved. After deploying a fix, retest in DevTools to verify the shift is gone, clear any caches (including your CDN and optimization plugin output), and check all affected templates—not just the one page you tested on. Then give the field data a few weeks to catch up.

How to fix CLS in WordPress: Five causes

Now that you know what’s shifting, it’s time to fix it. CLS problems in WordPress tend to fall into five categories:

  1. Images, videos, and media without fixed dimensions.
  2. Web fonts that swap in with different metrics.
  3. Ads, embeds, and iframes that load late.
  4. Cookie banners, sliders, and dynamic content injected by JavaScript.
  5. CSS, JavaScript, and animations—including optimization plugins that accidentally make things worse.

If your diagnosis pointed to a specific cause, jump straight to that section. Otherwise, read through all five—it’s worth ruling each one out.

1. Images, videos, and media without fixed dimensions

When a browser starts rendering your page, it lays out the content top to bottom. If it encounters an image or video without knowing its dimensions, it reserves zero space—then shoves everything below it downward the moment the file loads. That’s a layout shift.

The fix: Add explicit width and height attributes to every <img> and <video> element that affects your layout. For example:

<img src="hero.jpg" width="1200" height="630" alt="Hero banner" />

A common worry here is that hard-coded dimensions will break responsive layouts. They won’t—as long as you pair them with this CSS:

img {
  max-width: 100%;
  height: auto;
}

For even more control across breakpoints, use the aspect-ratio CSS property on the image or its container. Just make sure the ratio matches your actual image dimensions, and adjust it per breakpoint if the crop changes.

The lazy load trap.

Since WordPress 5.5, the platform adds loading="lazy" to images by default. That’s great for below-the-fold content—but if your logo, hero image, or banner gets lazy-loaded, the browser delays fetching it, and the space collapses until it arrives. Fix this by adding loading="eager" to above-the-fold images, or use your performance plugin’s exclusion setting.

WordPress updates: Since WordPress 6.9, the core Video block now outputs width, height, and an inline aspect-ratio style automatically, which eliminates CLS for native video embeds. Custom videos, page-builder video widgets, and older WordPress installs still need manual sizing. WordPress 7.0 takes this further with smarter image-loading prioritization—hidden images within navigation overlays and interactive blocks no longer compete with visible above-the-fold content, helping browsers focus rendering resources where they matter most for CLS and LCP.

One more thing to watch: SVG logos. WordPress doesn’t always calculate dimensions for SVGs, so your header can shrink or expand on first paint. Set explicit sizes in your theme settings or via CSS.

NitroPack equivalent:

Preemptive Image Sizing automatically adds missing width and height attributes on all plans. On Plus plans and above, LCP Preload identifies the above-the-fold element and excludes it from lazy loading—no manual configuration needed.

2. Web fonts

Ironically, the most common advice for fixing invisible text during font loading—adding font-display: swap—can actually cause layout shifts.

Why? Because swap tells the browser to show text immediately using a fallback system font, then replace it with your custom font once it downloads.

That’s great for visibility. But if the fallback and custom fonts have different character widths, line heights, or spacing, the swap pushes surrounding content around. You’ve traded invisible text for a layout shift.

So what does actually work?

  • Preload your critical font files. Adding <link rel="preload"> for the one or two WOFF2 files needed above the fold gives the browser a head start on downloading them. The font arrives earlier, which shrinks the window where a swap can happen. But preloading alone doesn’t eliminate the swap—it just makes it less likely to cause a visible shift.
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
  • Use font-display: optional for zero-swap stability. This gives the font an extremely short window to load. If it misses that window, the browser sticks with the fallback—no swap, no shift. The tradeoff is that on slow connections, visitors might not see your custom font at all. 
  • Tune your fallback font metrics. The CSS properties size-adjust, ascent-override, and descent-override let you match your fallback font’s dimensions to your custom font, so even when a swap does happen, nothing shifts

One practical note: Preloading works best when you control the font file URLs. If you’re using Google Fonts loaded from Google’s servers, the URLs can change. Self-hosting your fonts with a plugin like OMGF gives you stable, predictable URLs and full preload control.

NitroPack equivalent:

Override Font Rendering Behavior lets you set font-display values in three clicks—no CSS editing required. On Plus plans and above, Font Loading Strategy automatically adds preload tags for your font files, and Google Fonts are served from NitroPack’s CDN for faster delivery.

3. Ads, embeds, and iframes

Dealing with ads and embeds can be quite frustrating, because you’re often working with content you don’t fully control.

The core problem is the same: Without your guidance, the browser doesn’t know how big the content will be until it loads, so it reserves no space. Then an ad, a YouTube embed, or a social media widget pops in and shoves everything below it downward.

The fix: wrap third-party content in a container that reserves space upfront. 

Example of previously reserved space for ads by Le Monde.

For a standard display ad, that can be as simple as setting a min-height on the wrapper:

<div class="ad-slot" style="min-height: 250px;"></div>

For responsive embeds like YouTube videos, an aspect-ratio wrapper works better than a fixed height because it scales with the container width:

.video-embed {
  aspect-ratio: 16 / 9;
  width: 100%;
}

The AdSense complication.

Responsive AdSense units don’t have a single fixed size—Google calculates the ad dimensions dynamically based on layout and device. Setting min-height to the largest size you expect can reduce or eliminate shifts from bigger ads, but you’ll get blank space when a smaller ad serves. It also won’t guarantee zero CLS in every situation, but it’s a significant improvement over no placeholder at all.

Where you place ads matters, too. An ad slot at the very top of the page displaces the maximum amount of content when it loads. Moving ad placements to sidebars, between content sections, or below the fold reduces the visible impact of any shift—even if the shift technically still occurs.

Don’t forget about iframes. YouTube and Vimeo embeds without explicit width and height attributes shift content exactly like images do. Always include dimensions on your <iframe> elements.

An honest note on scope:

No performance optimization tool—NitroPack included—manages AdSense placeholder sizing. Responsive ad units depend on fill data from Google’s ad auction that no caching or optimization layer has access to. If you’re running ads and need fine-grained placeholder control, use a dedicated ad management plugin like Advanced Ads or Ad Inserter

This category covers everything that gets injected into your page by JavaScript after the initial HTML has already rendered. These shifts are sneaky—they don’t show up in your source code, and they’re easy to miss in lab testing if you don’t interact with the page the way a real visitor would.

A much bigger CLS source than most people realize. Some GDPR/cookie plugins inject a banner directly into the normal document flow after the page has painted. The banner appears, pushes everything below it down, and that’s a layout shift—even though the banner itself looks fine to the site owner who’s used to seeing it.

The fix depends on how your plugin works. 

  • If you can configure the banner to use position: fixed or position: sticky, do that—it overlays the page content instead of displacing it. 
Example of a cookie consent banner as an overlay by El Diario.
  • If your plugin doesn’t offer that option, you can reserve space for the banner from the start by adding a placeholder div with the same height at the top of your page. 

Or, if the plugin is the problem, switch to one that renders as an overlay by default. A lot of sites also use pop-ups for these now, so there’s no shift whatsoever. 

4 - Example of a cookie consent in a pop-up from Business Insider

Sliders and carousels above the fold 

These render at one size using the initial HTML, then re-render at a different size once their JavaScript finishes calculating dimensions. This isn’t a lazy load issue—it’s a JS re-render. The container starts at one height, then jumps to another.

Give the slider container a fixed height that matches the largest slide. If that’s not possible, move the slider below the fold where the shift has less impact on your CLS score.

The JS re-render pattern in general

Any element that renders, then changes size when its script finishes, causes this type of shift. Review widgets, testimonial carousels, and pricing tables that load via JavaScript all do it. The universal fix is a fixed-height parent—the content can re-render inside the container all it wants, but the surrounding layout stays put.

Page builders

Elementor sliders, Oxygen widgets, and theme-bundled review blocks can all generate CLS in code you don’t have direct access to edit. Start by checking the builder’s own height and dimension settings—many have options buried in their advanced panels. If no setting exists, apply custom CSS to the wrapper element to lock its dimensions. 

Be aware that caching and optimization tools—NitroPack included—won’t rewrite page-builder markup. These shifts live in the builder’s output, and the fix has to happen there too.

5. CSS, JavaScript, and animations

Here’s the counterintuitive one: the optimization plugin you installed to improve performance can actually make CLS worse. This applies to any caching or speed tool when misconfigured.

Three ways it happens:

  • Lazy-loading above-the-fold images without reserving space (covered in section 1).
  • Async CSS generating FOUC—when stylesheets load asynchronously, unstyled. content flashes on screen, then jumps once styles apply.
  • Deferred JavaScript breaking above-the-fold elements—a header menu or sticky nav that depends on a script won’t render correctly if that script is deferred.

So, how can you fix this? These are a few ways: 

  • Fix FOUC with critical CSS. Inline the styles needed for above-the-fold content directly in the <head>, then load the rest asynchronously. Most performance plugins offer this—the trick is making sure it’s actually enabled and generating correctly for each page template.
  • Fix deferred JS selectively. Don’t exclude all jQuery or your entire theme script from deferral. Instead, identify only the specific scripts your above-the-fold layout depends on and exclude those—plus their dependencies.

Non-composited animations are a separate issue. PageSpeed Insights flags “Avoid non-composited animations” when your CSS animates properties like top, left, width, height, margin, or padding—these trigger layout recalculation every frame. Switch to transform instead:

  • Use transform: translateX(10px) instead of left: 10px
  • Use transform: scale(1.1) instead of width: 110%

Transforms run on the GPU compositor and don’t recalculate layout.

NitroPack equivalents:

LCP Preload (Plus+) handles above-the-fold lazy load exclusion automatically. Per-page critical CSS generation addresses FOUC. Delayed Scripts includes exclusion controls for scripts that above-the-fold elements depend on. But any tool needs proper configuration—the exclusion controls exist because they’re needed.

WordPress plugins that automate CLS fixes

One thing worth saying upfront: no plugin or service can fix everything. The tools below automate the structural causes of CLS—missing image dimensions, font preloading, critical CSS, lazy load exclusions—but they can’t rewrite your theme code, fix page builder animations, or manage ad placeholder sizing. If your CLS comes from those sources, you’ll need the manual fixes from the sections above.

With that said, here’s how the most popular options compare:

SolutionAdds missing image dimensionsFont preloadCritical CSSLazy load exclusionsAd placeholdersCDN
NitroPack (free + paid)✓ (Preemptive Image Sizing, all plans)✓ (Plus+)✓ (per-page)✓ (LCP Preload, Plus+)✗✓ (built-in)
WP Rocket (paid)✓✓✓ (Remove Unused CSS)Manual✗Add-on (RocketCDN)
Perfmatters (paid)✓ âœ“✗✓ (“Exclude from lazy load”)✗✗
Jetpack Boost (free + paid)✗ (Image Guide identifies, doesn’t auto-add)✗✓ (free manual / paid auto)✗✗✓ (Image CDN)
Asset CleanUp (free + Pro)✗✓ Local fonts (free)✓ Advanced (Pro)✓ (Pro)✓ (per-asset unload rules)✗✗

Notice that no plugin covers ad placeholders, so if you run ads, you’ll still need a dedicated ad management plugin regardless of which performance tool you choose.

Combining several of these tools can work if you’re comfortable configuring each one separately. The tradeoff is conflict risk and maintenance time—when two plugins both try to handle critical CSS or lazy loading, things can break in ways that are hard to debug.

NitroPack covers more columns under a single configuration because it bundles caching, image optimization, font handling, critical CSS, and a Cloudflare-powered CDN into one service. That’s useful if you’d rather not maintain a stack of separate plugins. It’s less useful if you want granular, per-feature control over every optimization.

Want to test it first? NitroPack’s free plan covers one site with 1,000 pageviews per month—no credit card required.

WordPress layout shift FAQs

Why is mobile CLS worse than desktop?

Three reasons. 

  1. Smaller viewports magnify every shift—an element moving 50 pixels takes up a much larger percentage of a phone screen than a desktop monitor. 
  2. Mobile CPUs are slower, which delays JavaScript-driven size calculations (sliders, carousels, review widgets all render later). 
  3. Weaker mobile connections delay font and image downloads, widening the window for late swaps and missing dimensions. 

Google scores mobile and desktop separately, so passing on desktop doesn’t mean you’re safe on mobile.

Does CLS affect SEO rankings?

Indirectly. CLS is one of three Core Web Vitals that Google uses as a page-experience signal, alongside LCP and INP. The assessment requires passing all three metrics at the 75th percentile—failing just one means failing the whole thing. It won’t tank your rankings on its own, but fixing it removes one barrier to better visibility.

Can I fix CLS without installing a plugin?

Yes. Every manual fix we covered—adding width/height to images, using aspect-ratio, reserving space with min-height containers, setting iframe dimensions, preloading fonts—works without any plugin. The tradeoff is configuration time and ongoing maintenance whenever your theme, plugins, or WordPress itself updates. If that sounds like more upkeep than you want, the plugin comparison above shows your options.

Can I fix CLS in WordPress for free?

Yes, the manual fixes above cost nothing. And if you want some automation, NitroPack’s free plan includes Preemptive Image Sizing and critical CSS for one site with 1,000 pageviews per month—no credit card required.

Lora Raykova

By Lora Raykova

User Experience Content Strategist

Lora has spent the last 8 years developing content strategies that drive better user experiences for SaaS companies in the CEE region. In collaboration with WordPress subject-matter experts and the 2024 Web Almanac, she helps site owners close the gap between web performance optimization and real-life business results.