RevealTheme logo
Back to Blog

WordPress Lazy Loading: The Edge Cases That Cause Problems

WordPress Lazy Loading: The Edge Cases That Cause Problems
The RevealTheme Team

By

··Updated May 27, 2026·4 min read

WordPress lazy loading mostly works. Since 5.5 the core inserts loading="lazy" on images, since 5.7 it does the same for iframes, and since 6.3 it actively tries to protect your Largest Contentful Paint by skipping the first few in-content images and tagging the likely hero with fetchpriority="high". For a stock theme serving stock images, you can do nothing and come out ahead.

So in 2026 the interesting failures are rarely "lazy loading is off." They are conflicts and blind spots: core making a wrong guess, a plugin undoing core's good guess, or an image core never sees in the first place. This article is organised around why each failure happens and how to diagnose it, because once you know the mechanism the fix is obvious.

What core actually does now (so you can tell when it's wrong)

Two filters do almost all the work, and knowing their defaults tells you when behaviour is expected versus broken.

  • wp_omit_loading_attr_threshold (added in 5.9) controls how many of the first content media elements skip lazy loading entirely. The default was 1 until 6.3, then bumped to 3. That is why the first few images on a fresh install load eagerly while everything below is deferred.
  • wp_get_loading_optimization_attributes assigns fetchpriority="high" to the first non-lazy image that is larger than 50,000 square pixels (roughly 224×224 and up). The pixel floor is itself tunable via wp_min_priority_img_pixels.

The practical takeaway: core's hero detection is positional and size-based, not semantic. It assumes your LCP element is one of the first large images in the main content. When that assumption holds, leave it alone. Every edge case below is a situation where it doesn't.

Edge case 1: your hero isn't "in the content"

Core's threshold counts images within the post body and certain template parts. A full-width hero rendered by the theme header, a page-builder section, or a block pattern outside the main content flow may not be counted by the omit-threshold logic at all. The result is a hero that gets loading="lazy" anyway, delaying LCP by exactly the amount the deferral saves on everything else.

Diagnose it by viewing source and searching for your hero's filename. If it carries loading="lazy" and no fetchpriority, core didn't recognise it. The fix depends on how the image is rendered: a theme template should drop the lazy attribute and add fetchpriority="high" for that specific output, while a page builder usually exposes a per-image "disable lazy load" or "above the fold" toggle. Elementor, Bricks, and Breakdance all have this, though it is often buried in advanced image settings.

Edge case 2: a plugin and core both want to manage loading

This is the most common 2026 problem and the one the old advice misses entirely. Perfmatters, WP Rocket, FlyingPress, and NitroPack all ship their own lazy-loading and LCP-image logic. Turn one on without telling it about core, and you get two systems fighting:

  • The plugin re-lazies an image core deliberately left eager, pushing the lazy attribute back onto your hero.
  • The plugin strips or never re-applies fetchpriority="high", so the browser no longer prioritises the LCP request.
  • Both inject loading="lazy", producing duplicate or conflicting attributes that some HTML minifiers then mangle.

The rule: pick one authority. If you run a performance plugin with its own image handling, use its exclusion list to mark above-the-fold images rather than relying on core's threshold. Perfmatters' "Lazy Load → Exclude" and "Critical Images" fields, WP Rocket's "Excluded Images" and LCP automation, and FlyingPress's per-element exclusions exist precisely so the plugin's view of the hero overrides core's guess. Add the hero's filename or CSS class there, confirm in view-source that exactly one system is acting, and move on.

Edge case 3: assets core literally cannot see

The loading attribute only exists on <img> and <iframe>. Two categories slip past it completely:

CSS background images

background-image: url(...) has no native lazy-loading. The browser fetches it whenever the element is styled, which for a full-page hero background means immediately. If that background is genuinely your LCP element, no loading attribute will ever help it because there is no img tag to tag. The honest fix is structural: if the image is content, render it as an actual <img> (or a <picture>) so core's priority logic can reach it. For decorative, small, repeated patterns, lazy loading isn't worth the effort — the file is a few KB and deferring it saves nothing meaningful.

JavaScript-injected images

Carousels, masonry galleries, and "load on interaction" widgets often insert images via JS after the HTML is parsed. Core never sees those markup nodes during PHP rendering, so neither the omit-threshold nor the fetchpriority logic applies. Real lazy loading here is done with IntersectionObserver — which is exactly what the performance plugins use under the hood for background images and injected elements. If you need it and your plugin doesn't cover the widget, a small IntersectionObserver that swaps a data-src into src as the element nears the viewport is the standard pattern.

Edge case 4: lazy loading without dimensions causes layout shift

Lazy loading and Cumulative Layout Shift are a pair. When an <img> has explicit width and height (or a CSS aspect-ratio), the browser reserves the box before the bytes arrive, so deferring the load shifts nothing. Strip the dimensions and the deferred image pops in late, shoving content downward — visible, measurable CLS.

Core's own image insertion always writes dimensions, so the default path is safe. The failure shows up in custom theme code and ACF/templates that build <img> tags by hand and omit width/height. Diagnose with the Layout Shift overlay in Chrome DevTools (Performance panel, or the Web Vitals extension); if a lazy image is the shifting node, add explicit dimensions and the CLS disappears. Keep your CLS under the 0.1 "good" threshold and your LCP under 2.5s — those two numbers are the whole reason this attribute exists.

Edge case 5: iframes you actually want loaded

Lazy iframes are usually a big win — a single YouTube or Maps embed can pull 300KB to well over 1MB before the user ever scrolls to it. But the same deferral hurts when the iframe is something the visitor expects immediately: an embedded booking widget or contact form near the top of the page. Lazy loading it adds a perceptible delay at the exact moment of intent.

For above-the-fold interactive embeds, remove the lazy attribute for that iframe specifically. For heavy media embeds further down, lazy loading is almost always correct — and for YouTube in particular, a façade plugin (or core's own embed optimisations) that shows a thumbnail and only loads the player on click beats raw lazy loading outright.

Edge case 6: PageSpeed flags a lazy image — is it right?

PageSpeed Insights and Lighthouse routinely warn "Lazy-loaded LCP element" or surface a deferred image under LCP opportunities. The warning is only correct if that image is genuinely your LCP element, and the tools sometimes misidentify it.

Verify before you act. Open Chrome DevTools, run a Performance trace, and look at the LCP marker — it names the exact element. Or use the Web Vitals extension, which reports the LCP node directly. If the lazy-loaded image is that node, remove its lazy attribute and let core (or your plugin) give it fetchpriority="high". If it isn't — a common false positive on pages where the LCP is actually a heading or background — you can safely ignore the warning. Never strip lazy loading from a below-the-fold image just to silence a tool; you'd be loading bytes early for no benefit.

A two-minute verification pass

Whatever the symptom, the same check confirms the fix:

  1. Open a representative page in Chrome DevTools → Network, filtered to Img.
  2. Hard reload without scrolling. Note which images fetch immediately — that set should be your above-the-fold images and nothing more.
  3. Scroll slowly. Below-the-fold images should fetch as they approach the viewport, not all at once on load.
  4. View source and confirm your hero has no loading="lazy" and does have fetchpriority="high" — applied by exactly one system, not two.

If above-the-fold images load late, you're in edge case 1 or 2. If everything loads eagerly at once, a plugin disabled core's lazy loading without replacing it. If a background or injected image won't defer, that's edge case 3. The symptom tells you the mechanism, and the mechanism tells you the fix.