Skip to content
WP speed & Core Web Vitals

Lazy Loading on WordPress: Native vs Plugin (And When to Disable It)

WordPress lazy-loads images natively since 5.5. Here is when core is enough, when a plugin still helps, and when lazy loading silently hurts your LCP.

By WitsCode8 min read

WordPress already lazy-loads images by default. It has done so since version 5.5 in August 2020, using the browser-native loading="lazy" attribute that Chrome, Edge, Firefox, and Safari all support without a single line of JavaScript. If you are about to install a lazy-loading plugin in 2026 because someone told you it would help your Core Web Vitals, the more honest first step is to open the page source, search for loading="lazy", and watch it appear on every image inside the post content. The core feature works.

The trap, the part that quietly damages real sites, is the hero image. When the largest image on a page is lazy-loaded by default, the browser will not fetch it until it judges the image close to the viewport, which on a hero-driven landing page means the Largest Contentful Paint waits on a network request that should have started immediately. WordPress core fixed the most obvious case of this in version 5.9, but the fix is a heuristic, and page builders, custom themes, and featured-image templates routinely defeat it. The right answer for almost every WordPress site is to trust native lazy loading for everything below the fold and to fight aggressively to make sure the hero image is not part of it. That is the article in two paragraphs. The rest is mechanics.

What native lazy loading actually does

The loading="lazy" attribute is a hint that tells the browser the image is not critical to first paint and can wait until the user scrolls toward it. WordPress automatically adds the attribute to image tags in post content, widget areas, comments, and the template parts that block themes render through wp_filter_content_tags(). It applies to iframes too, since version 5.7. The attribute only attaches to images that already declare width and height, because a lazy image without dimensions causes the browser to reflow the page when the image arrives, which inflates Cumulative Layout Shift and undoes the speed gain.

Chrome, the dominant browser for most marketing sites, defers a lazy image until the user scrolls within a threshold of it. The threshold is dynamic. On a fast connection it sits roughly twelve hundred pixels below the fold. On a slow connection the browser widens the threshold so the image has time to arrive before the user reaches it. Firefox and Safari behave similarly, with their own thresholds. None of them require the page to ship JavaScript. The browser does the work.

This matters because a lazy-loading plugin that uses an Intersection Observer to swap data-src to src does the same thing, only worse. The plugin must wait for its JavaScript to load and execute before it can defer anything, which means images briefly request normally before being intercepted, or alternatively the plugin replaces the src with a placeholder and only swaps it in after the script runs. Either path adds work the browser would otherwise have done for free. Native lazy loading is faster, lighter, and built in.

The hero image trap

Browsers cannot tell which image on a page is the Largest Contentful Paint candidate before they render. WordPress cannot tell either, but it has a reasonable guess: the first image inside the post content is probably the visually dominant one. Since WordPress 5.9, core skips loading="lazy" on the first content image, leaving it to load eagerly. For a blog post with a featured image at the top, this is exactly correct. The hero loads immediately, everything else lazies.

The problem is that many WordPress sites do not place their hero image inside the_content(). A custom theme might render the featured image in header.php, outside the filter that strips lazy loading. A page builder like Elementor, Bricks, or Gutenberg's site editor places hero blocks in template parts that use their own rendering paths. A theme designer might write a hero section that pulls the image from an Advanced Custom Fields field and outputs it through a partial. In every one of these cases, the core heuristic does not see the image, does not strip the lazy attribute, and the LCP candidate quietly waits behind the rest of the page.

You can verify this in thirty seconds. Open the page in Chrome, open DevTools, switch to the Performance panel, record a load, and look at which image fires the LCP event. Then open the Elements panel and inspect that image. If you see loading="lazy" on it, the hero is being deferred, your LCP is slower than it needs to be, and no caching plugin is going to fix it. The fix is to remove the attribute.

The fetchpriority rule

Telling the browser an image is not lazy is half the answer. The other half is telling it the image is the most important resource on the page. The fetchpriority="high" attribute, supported in Chrome since version 102 and now a stable web platform feature, instructs the browser to upgrade the resource to the highest fetch priority, ahead of CSS background images, ahead of fonts that have not been preloaded, ahead of any other media. On a hero-driven page, this single attribute typically saves between two hundred and six hundred milliseconds of LCP, because the browser stops waiting in line and starts fetching the hero in parallel with the critical CSS.

The combined rule for the LCP image, whatever theme or builder you use, is loading="eager" fetchpriority="high". Loading eager overrides any plugin or filter that might be adding lazy loading. Fetchpriority high pushes the image to the front of the network queue. If the image is large enough to justify it, a <link rel="preload"> hint in the document head adds another small win by telling the browser to start the request before it has even parsed the body. Three lines of HTML, no plugin needed, and the LCP problem most WordPress sites carry around quietly disappears.

When a plugin still helps

Native lazy loading covers <img> tags and <iframe> tags. It does nothing for images loaded through CSS, which is a problem because half of WordPress's visual identity lives in CSS background-image declarations. The hero block on most page-builder pages is a <div> with a background-image set inline or in a stylesheet. Section dividers, parallax bands, decorative banners, full-bleed feature blocks, and the testimonial card backgrounds in theme demos are all CSS-loaded images. The browser fetches them when it parses the CSS, which in modern WordPress is during the critical render path. There is no native attribute to defer them.

This is where a focused lazy-loading plugin still earns its place. Plugins like WP Rocket, Perfmatters, and the standalone a3 Lazy Load extension can intersection-observe DOM nodes and swap a data-bg attribute into the live background-image only when the node enters the viewport. The same plugin will usually handle iframe edge cases, in particular the YouTube and Vimeo embeds that some helpers inject as raw HTML outside the content filter where core would normally process them. The most valuable feature in this category is the click-to-play video preview: replacing an embedded YouTube iframe with a static thumbnail that only loads the player when clicked. A YouTube iframe ships roughly one and a half megabytes of JavaScript before the user has decided to watch. A click-to-play preview ships a single image. The savings are immediate and large.

The recommendation is not to install a plugin to lazy-load images, because core already does that. The recommendation is to install a plugin that lazy-loads the things core misses, and to configure it so it does not duplicate or override core's image handling. Most plugins have a setting called something like "skip the first image" or "exclude the first N images". Turn it on. Set the number to match the actual number of above-the-fold images on the page, which on a typical hero design is one.

When lazy loading actively hurts

A site that should not lazy-load anything is one where every image fits above the fold, which describes most single-page restaurant sites, portfolio one-pagers, and short landing pages. A site that lazy-loads its hero is the worst case, and it happens more often than it should. A site with a slider as the first content element is almost guaranteed to lazy-load every slide except the first, which would be fine, except that some slider plugins lazy-load the first slide too. Site owners then add a caching plugin, a WebP plugin, and a lazy-loading plugin, and the three fight each other over which one strips and which one re-adds the attribute. The result is unpredictable LCP that varies by which plugin's filter ran last in the request.

The diagnostic is simple. If your LCP is above two and a half seconds and your hero image is your LCP candidate, lazy loading is suspect. Disable any third-party lazy-loading plugin first, retest, and confirm the hero is now loading eagerly. If LCP improves, the plugin was the problem. If LCP does not improve and the hero still has loading="lazy", the theme or page builder is rendering it through a path that core does not see, and you need to add loading="eager" and fetchpriority="high" directly in the template or via a the_content filter.

Disabling native lazy loading entirely

WordPress exposes a filter for the rare case where you want to turn the feature off. A single line in functions.php does it:

add_filter( 'wp_lazy_loading_enabled', '__return_false' );

This is appropriate for single-page sites where every image is above the fold, for landing pages built around hero galleries that should all preload, and for archives where the layout depends on every image being measured immediately. It is almost never appropriate for content sites, blogs, or e-commerce listings, because the bandwidth and CPU savings of deferring offscreen images on those layouts are real and large. The default is correct for most WordPress sites. The defaults are correct, mostly. The exception is the hero, and the exception is fixable in three lines of HTML.

What WitsCode does on a build

Every site we ship is checked against a short lazy-loading audit before launch. We open Chrome DevTools, identify the LCP candidate, confirm the image is not lazy-loaded, confirm it carries fetchpriority="high", and confirm that no plugin is interfering with the attribute. We disable any plugin that duplicates core's image lazy loading and keep only the ones doing work core does not do, which usually means iframe handling and CSS background images. We do not install a lazy-loading plugin reflexively because we have measured what core gives us for free, and we know how to extend it where it falls short.

If you have inherited a WordPress site and the LCP is slow, the lazy-loading attribute on the hero image is the first thing to check. The fix is small. The improvement, on a site where it applies, is usually obvious within a single Lighthouse run.

Get weekly field notes.

Practical writing on shipping products, straight to your inbox. No spam.

Need help with this?

WordPress Development

We design and build web apps, MVPs, and SaaS products. Talk to us about what you are working on.

Talk to us

Want to discuss wp speed & core web vitals for your business?

Start a project and we'll talk through where you are, what's working, and the highest-leverage moves for the next 90 days.