Image Lazy-Loading Rate in WordPress: Diagnose Issues and Fix Without Breaking LCP

WordPress applies native lazy loading since 5.5, but plugins and themes often break it. How to diagnose and fix.

Since WordPress 5.5 (2020) core automatically attaches loading="lazy" to every image inside post content. The browser uses it to defer downloading images outside the initial viewport - a major reduction in initial page weight. Plenty of plugins, themes, and page builders break the behavior, however: they call the_post_thumbnail() outside core helpers, force loading="eager" on every image, or hand-build the markup as a raw string.

Why this matters

On a 30-image page that scrolls long, only 3-5 images appear above the fold. Loading all 30 immediately means 5MB+ on first paint. With lazy loading, the first 500KB suffice and the rest stream in as the user scrolls. That maps directly to 30-50% faster LCP, lower Total Blocking Time, and higher Lighthouse scores.

Critical caveat: the first image in the viewport (the LCP candidate) must be eager, not lazy. Lazy on the LCP element forces the browser to wait until it confirms viewport intersection - that adds 100-300ms to LCP. WordPress 5.9+ tries to detect the LCP candidate and mark it fetchpriority="high" automatically, but it does not always succeed.

How to detect

Easy version: view source on the homepage, search for <img, and count how many include loading="lazy". Below 70% means trouble.

Cleaner version: F12 > Console, run:

const imgs = document.querySelectorAll('img');
const lazy = [...imgs].filter(i => i.loading === 'lazy').length;
console.log(`${lazy}/${imgs.length} (${Math.round(lazy/imgs.length*100)}%)`);

LCP-specific check: F12 > Lighthouse > Performance > Run audit. Under "Largest Contentful Paint element" look at the offending image - confirm it is not lazy.

How to fix

Page builders first: in Elementor (Settings > Performance > Lazy Load Images = ON), Divi (Theme Options > General > Performance), or Bricks (Settings > Performance) - enable lazy.

Check functions.php and any plugin for a global kill switch:

// remove this if you find it
add_filter('wp_lazy_loading_enabled', '__return_false');

For custom theme code, prefer core helpers - they apply the filter chain that adds lazy:

// good - flows through filters, picks up lazy automatically
echo get_the_post_thumbnail($post_id, 'large');

// or:
echo wp_get_attachment_image($attachment_id, 'large');

Avoid hand-rolled <img src="..." /> markup without explicit lazy.

For the LCP candidate (hero image), force eager:

<img src="hero.jpg" loading="eager" fetchpriority="high" alt="..." />

For iframes and CSS background images, install a3 Lazy Load or FlyingPress - core only handles <img>.

Common mistakes

Mistake one: lazy-loading the first viewport image. LCP gets delayed every single time. Always pair the hero/cover with eager + fetchpriority=high. Mistake two: stacking a third-party lazy plugin on top of native lazy. The two mechanisms collide; one cancels the other on certain images. Pick one. Mistake three: relying on Cloudflare Mirage or Polish alone - they operate at the network layer and do not patch every image attribute reliably.

Verifying the fix

Re-run the Console snippet - 90%+ of images should be lazy (the first one or two stay eager). PageSpeed Insights LCP should fall 200-500ms. In DevTools Network, reload and confirm images near the bottom of the page do not appear in the initial request waterfall - only after scrolling. WebPageTest Visual Progress should show the top of the page rendering faster.

Tip: If Lighthouse LCP does not improve after adding lazy loading, your LCP candidate may be text rather than an image, or it may already be eager. Inspect Lighthouse's "Largest Contentful Paint element" section to confirm what to optimize.