Lazy loading delays loading images and iframes until they’re about to scroll into view. Pages load fewer bytes up front, which helps mobile visitors and long pages. WordPress has done this natively since version 5.5, by adding loading="lazy" to images (and to iframes since 5.7). Used wrongly, though, lazy loading hurts your Largest Contentful Paint (LCP). Here’s how to get it right.
How native lazy loading works
WordPress outputs:
<img src="photo.jpg" width="1200" height="800" loading="lazy" decoding="async" alt="…">
- The browser decides when to fetch the image, based on its distance from the viewport. No JavaScript is needed.
widthandheightreserve space, so the layout doesn’t jump (CLS).- It applies to images in content, featured images, widgets, avatars, and embedded iframes (YouTube and others).
The LCP trap
The largest image above the fold (hero, featured image, or the first image in a post) is usually the LCP element. If it has loading="lazy", the browser waits until layout is done before fetching it, which can add hundreds of milliseconds.
WordPress (6.3+) tries to avoid this:
- it skips
loading="lazy"on the first content image(s), using a heuristic; - it adds
fetchpriority="high"to the image it considers the LCP candidate.
Themes and page builders can confuse the heuristic. Check it:
- Run PageSpeed Insights on the page.
- Look at “Largest Contentful Paint element”, and the diagnostic “Largest Contentful Paint image was lazily loaded”.
- Or, in your browser’s DevTools, inspect the hero image’s attributes.
Control it with filters
Don’t lazy-load a specific image (for example in your theme template):
echo wp_get_attachment_image( $id, 'large', false, [ 'loading' => 'eager', 'fetchpriority' => 'high' ] );
Change how many content images are skipped (default 3 in recent versions, theme-dependent):
add_filter( 'wp_omit_loading_attr_threshold', fn() => 1 );
Disable native lazy loading entirely (rarely needed; for example if you use a different solution):
add_filter( 'wp_lazy_loading_enabled', '__return_false' );
Put snippets like these into a small site-specific plugin, rather than a theme you might replace.
Preload the hero image
For a hero image defined in CSS (background-image), the browser discovers it late. Preload it in the page <head>:
<link rel="preload" as="image" href="/wp-content/uploads/hero.webp" fetchpriority="high">
Many performance plugins have a setting for this. Better still, use a real <img> element for hero images.
Iframes and videos
YouTube and Vimeo embeds are heavy (several hundred KB of scripts each). Native loading="lazy" helps. For even lighter pages, use a “lite” embed (a thumbnail that loads the player on click): many performance plugins offer this, or use the lite-youtube web component.
Plugins and double lazy loading
Older JavaScript lazy-load plugins (and some optimization plugins) replace src with data-src and load images with JavaScript. Problems:
- double handling with native lazy loading;
- images invisible without JavaScript, and to some crawlers;
- the LCP image delayed even more.
If your plugin has a “lazy load images” option, either use native mode, or turn the plugin’s option off and rely on WordPress core. Make sure the LCP image is excluded.
When lazy loading doesn’t help
- Short pages where everything is above the fold.
- Tiny images (icons, logos). The overhead is pointless. Inline SVG icons.
- When the real problem is the server: slow TTFB (time to first byte) delays everything. Fix it with page caching and a fast VPS. See Nginx FastCGI cache and Core Web Vitals and server performance.
Quick reference
| Goal | How |
|---|---|
| Native lazy loading | on by default (loading="lazy") |
| Keep the LCP image fast | loading="eager" + fetchpriority="high" |
| Fewer skipped images | wp_omit_loading_attr_threshold filter |
| Disable everywhere | wp_lazy_loading_enabled → false |
| Heavy video embeds | lite embeds (click to load) |
Related: WordPress image optimization and WordPress performance tuning.