WordPress Image Lazy Loading - What It Is and How to Implement It Correctly

Lazy loading defers images until needed. WordPress enables it by default, but applying it to the LCP image hurts performance. Here is how to do it right.

Dobromir Dechev
Dobromir WordPress agency owner

Lazy loading is the practice of deferring image loading until the user is about to scroll to them. Images below the fold do not load until needed, reducing the initial page weight and speeding up the above-the-fold experience.

WordPress has supported native lazy loading since version 5.5 (via the loading="lazy" attribute on <img> tags). But default settings and common plugins sometimes apply it incorrectly, actually hurting performance.


How lazy loading works

When a browser encounters <img loading="lazy">, it does not fetch the image until the user scrolls within a threshold distance of it (typically 1200-1500px ahead of the viewport). For images well below the fold, this means they do not load at all until the user scrolls toward them.

<!-- Eager loading (default) - loads immediately -->
<img src="hero.webp" width="800" height="450" alt="Hero" loading="eager">

<!-- Lazy loading - deferred until near viewport -->
<img src="diagram.webp" width="600" height="400" alt="Diagram" loading="lazy">

The browser makes this decision before the page's CSS and JavaScript have fully loaded, so it works purely on the loading attribute value.


The critical mistake: lazy loading the LCP image

The most common performance error with lazy loading: applying loading="lazy" to the hero image or main above-the-fold image.

The LCP (Largest Contentful Paint) metric measures how long it takes for the largest visible element to render. If the LCP element is a lazy-loaded image, the browser intentionally delays fetching it, which directly hurts the LCP score.

Do not lazy load the LCP element.

WordPress 5.5 added loading="lazy" to all images globally. WordPress 6.3 added logic to identify the first image in the content and skip lazy loading on it. But this heuristic is not always correct - especially with page builders, custom templates, or images added above the post content.

How to identify your LCP element

  1. Open PageSpeed Insights on the page
  2. Under "Opportunities" or "Diagnostics", find "Largest Contentful Paint element"
  3. Click to expand - it shows which specific element is the LCP

If the LCP element is an image with loading="lazy", you need to remove the lazy attribute from that specific image.


Fix: Add fetchpriority="high" to the LCP image

The modern solution is to add fetchpriority="high" to the LCP image, which signals to the browser to fetch it as a high-priority resource:

<img
  src="hero.webp"
  width="1200"
  height="600"
  alt="Hero image"
  fetchpriority="high"
  loading="eager"
/>

fetchpriority="high" tells the browser to start fetching this image immediately, even before the parser has finished loading. Combined with loading="eager", this is the fastest possible image loading.

In WordPress: set fetchpriority on the hero

If you use the WordPress Customizer or a theme for the hero image, add a filter:

// Add fetchpriority to the first image in content
add_filter( 'wp_get_attachment_image_attributes', function( $attr, $attachment, $size ) {
    // Logic to identify if this is the LCP image
    // This example uses a custom field on the post to mark it
    global $post;
    if ( $post && get_post_meta( $post->ID, '_is_hero_image', true ) == $attachment->ID ) {
        $attr['fetchpriority'] = 'high';
        $attr['loading']       = 'eager';
    }
    return $attr;
}, 10, 3 );

For page builders (Elementor, Bricks), there is usually a setting on the image widget for "Lazy Load" - disable it for above-fold images and enable "fetchpriority high" if available.


WordPress default lazy loading behaviour

Since WordPress 5.5:

// All images in wp_get_attachment_image() get loading="lazy" by default
// Unless explicitly set to loading="eager"

// WordPress 5.9 added: skip lazy on first image (heuristic)
// WordPress 6.3 improved: better LCP detection

The default is reasonable for most images, but verify it is not applied to above-the-fold content on your specific templates.

To remove loading="lazy" from all images globally:

// Remove lazy loading filter
add_filter( 'wp_lazy_loading_enabled', '__return_false' );

Do not do this globally. It defeats the purpose of lazy loading for below-fold images. Instead, be surgical about which images get which loading behaviour.


Lazy loading in content (the editor)

Images inserted via the Gutenberg editor automatically get loading="lazy" applied by WordPress. This is correct for most in-content images (they appear mid-article and are typically below the fold for first-time visitors).

The exception is the first image in a post - if your post starts with a large featured image or a top-of-content image that appears above the fold, it should not be lazy loaded.

The image block in Gutenberg does not expose the loading attribute as a UI setting. To override it for a specific image, either:

  1. Use custom CSS to hint the browser (limited effectiveness)
  2. Use a code snippet filter targeting that specific image's attachment ID
  3. Use the block's "Advanced" tab to add a custom CSS class, then handle it in JS

Lazy loading for background images

CSS background images (background-image: url(...)) are not lazy loaded by browsers natively - the loading attribute only applies to <img> elements.

To lazy load CSS background images, use an Intersection Observer:

const lazyBackgrounds = document.querySelectorAll('.lazy-bg');
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const el = entry.target;
      el.style.backgroundImage = `url('${el.dataset.bg}')`;
      el.classList.remove('lazy-bg');
      observer.unobserve(el);
    }
  });
}, { rootMargin: '200px' });

lazyBackgrounds.forEach(el => observer.observe(el));

In HTML:

<div class="hero lazy-bg" data-bg="/images/hero.webp"></div>

Lazy loading and cumulative layout shift (CLS)

Images without explicit dimensions cause layout shift when they load, because the browser does not know how much space to reserve for them. This worsens CLS score.

Always add width and height attributes to images, even lazy-loaded ones:

<img src="post-image.webp" width="800" height="450" alt="..." loading="lazy">

The aspect ratio (800:450 = 16:9) lets the browser calculate and reserve the correct space before the image loads, preventing layout shift.

WordPress adds width and height attributes automatically when you insert images through the media library. Images added via custom HTML or some page builders may lack these - check and add them manually.


Summary of rules

  • Hero/above-fold LCP image: loading="eager" + fetchpriority="high"
  • In-content images, above fold: loading="eager"
  • In-content images, below fold: loading="lazy" (WordPress default)
  • Sidebar, footer images: loading="lazy"
  • All images: Always include width and height attributes

Was this article helpful?