loading="lazy" Not Working on Images and Iframes: How to Fix It
Native lazy loading looks like the simplest speed fix there is: add one attribute to an image or iframe and the browser waits until it is needed. Then you open the Network panel and see every image loading at once, or images that never appear, or a main image that loads late and drags down your Largest Contentful Paint. Most of the time the attribute is working as designed and the browser is simply loading earlier than you expected. The rest of the time something else on the page is overriding it, such as an old lazy-load script, an attribute added too late or images set in CSS. This guide explains how browsers really treat lazy content, how to find what is getting in the way, and how to test that it works.
What loading lazy actually does
The loading attribute on an img or iframe element tells the browser whether it may delay fetching that resource. With loading="lazy", the browser postpones the request until the element is within a certain distance of the viewport. With loading="eager", or no attribute at all, it fetches the resource straight away.
The important detail is that distance. Browsers do not wait until an image is on screen, because it would then appear blank as you scroll. They start the request well before, and further ahead on slower connections, so the file has time to arrive. Chrome uses a threshold measured in thousands of pixels on slow connections and somewhat less on fast ones. On a short page, that can mean every lazy image loads during the initial page load, which is correct behaviour rather than a failure.
| What you see | Usually means |
|---|---|
| Images just below the fold load immediately | Normal: they are within the browser's loading distance |
| Every image on a long page loads at once | A script or attribute is overriding lazy loading |
| Images stay blank or never load | A lazy-load script failed, or src is empty |
| Images load twice | Native and script lazy loading are both active |
| Background images ignore the attribute | Expected: the attribute does not apply to CSS |
| Main image loads late and LCP is poor | The hero is lazy-loaded when it should not be |
All current major browsers support lazy loading on images, and on iframes in recent versions. Browsers that do not support it simply ignore the attribute and load the resource normally, so it is safe to use. Chrome also does not lazy-load when JavaScript is turned off, as a privacy measure.
Why lazy loading seems not to work
The attribute is added too late
Some themes and plugins add loading="lazy" with JavaScript after the page has loaded. By then the browser has already read the HTML and started downloading every image. Check the original HTML with view source, not the Elements panel, which shows the page after scripts have changed it. When creating images in JavaScript yourself, set loading before src, because the request can start as soon as src is assigned.
An old lazy-load script is also running
Before native lazy loading, scripts such as lazysizes kept the real address in data-src and left src empty or set to a tiny placeholder. Mixing these with the native attribute causes trouble. A script that copies every data-src into src on page load defeats lazy loading entirely, while a script that fails to run leaves images blank. Pick one method, and for most sites that should be native lazy loading.
<!-- Script-based: the browser sees no real image until the script runs -->
<img src="placeholder.gif" data-src="photo.webp" alt="Garden patio">
<!-- Native: the browser sees the real address and decides when to fetch it -->
<img src="photo.webp" width="800" height="600" alt="Garden patio" loading="lazy">
CSS background images
The attribute applies only to img and iframe elements. A background set with background-image: url() loads when the browser applies the rule to an element in the layout, whatever attributes the element carries. To lazy-load a decorative background, add the rule with a class once the element approaches the viewport, using an IntersectionObserver, or change the image to an img element where that suits the design.
Hidden and carousel images
Images inside hidden tabs, accordions, menus and carousels behave differently depending on the browser and on how they are hidden. Some load when the page loads, some only when shown. If these matter to you, test each component in the browsers your visitors use rather than assuming. For carousels, keep the first slide eager and the rest lazy.
For a wider view of image loading, see fixing image lazy loading and image speed fixes.
Correct markup for images and iframes
A lazy image below the fold needs a real src, its dimensions and meaningful alt text:
<img src="/img/kitchen-after.webp"
srcset="/img/kitchen-after-600.webp 600w, /img/kitchen-after-1200.webp 1200w"
sizes="(max-width: 700px) 100vw, 700px"
width="1200" height="800"
alt="Kitchen after the refit, with oak worktops"
loading="lazy" decoding="async">
The width and height let the browser reserve the right space before the file arrives, so the content below does not jump when it loads. That matters more for lazy images than any others, because they often arrive while the visitor is reading. See image dimensions and image layout shift.
A video embed or map works the same way. Lazy loading an iframe can save a great deal, because a single video player can pull in hundreds of kilobytes of scripts:
<iframe src="https://www.youtube-nocookie.com/embed/VIDEO_ID"
width="560" height="315"
title="Product demo video"
loading="lazy"
allow="accelerometer; encrypted-media; picture-in-picture"
allowfullscreen></iframe>
Never lazy-load the first screen
Anything visible when the page first loads should load eagerly. The main image in particular should have no loading attribute and should carry fetchpriority="high" instead:
<img src="/img/hero-1200.webp" width="1200" height="600"
alt="Fitted wardrobes in a bright bedroom" fetchpriority="high">
A lazy hero image is one of the most common causes of a slow Largest Contentful Paint. The mobile LCP guide covers this in detail.
Platform notes
- WordPress: WordPress core has added
loading="lazy"to content images since version 5.5 and to iframes since 5.7, and in recent versions it leaves the first content images eager and may mark the main one withfetchpriority="high". Themes, page builders and optimisation plugins often add their own lazy loading on top. If a plugin offers lazy loading, either use it and turn off any others, or turn it off and rely on core. See images on WordPress. - Shopify: the
image_tagfilter adds lazy loading by default for images after the first sections. Check the hero section usesloading: 'eager'orpreload: true. - Next.js: the image component lazy-loads by default. Mark the hero with the
priorityprop, orpreloadin newer versions. - Page builders: many add their own lazy loading for backgrounds and sliders using scripts. Check each builder's performance settings.
A worked example
A travel blog adds loading="lazy" to all 40 images in a long guide, but the Network panel shows all 40 downloading as the page opens, around 6 MB in total. View source shows each image as <img src="blank.gif" data-src="..." loading="lazy">. The theme also bundles an old lazy-load script whose fallback mode copies every data-src into src as soon as the page has loaded.
The owner turns off the theme's lazy-load option, which removes the script and writes the real addresses back into src. Native lazy loading is now the only method. On reload, the Network panel shows the first five images loading, with the rest appearing as the page scrolls. The initial page weight falls to about 900 KB, and the hero image, which was also lazy, is changed to load eagerly with high priority.
The fix, step by step
Step 1
Check the original HTML
Open the page, then open view source with view-source: before the address, or press the shortcut for it in your browser. Search for loading= and check that the images and iframes below the fold carry loading="lazy" in the HTML itself. Do not rely on the Elements panel, which shows the page after scripts have changed it. If the attribute is missing from the source but appears in the Elements panel, it is being added too late and your theme or plugin needs changing.
Step 2
Test with the Network panel
Open Chrome DevTools, choose the Network panel, filter by Img and tick Disable cache. Reload the page without scrolling and count how many images loaded. Then scroll slowly to the bottom and watch new images appear in the list as you approach them. If images further down only load as you scroll, lazy loading works. If every image loads at once on a long page, something is overriding it, so move on to the next steps.
Step 3
Look for an old lazy-load script
In the page source, search for data-src, data-srcset and class names such as lazyload or lazy. Also check the list of scripts for names such as lazysizes or a theme lazy-load file. If you find them, a script-based method is running alongside or instead of the native attribute. Turn off the script in the theme or plugin settings so real addresses go back into src, then repeat the Network panel test to confirm the change.
Step 4
Use one lazy-loading method
Choose native lazy loading as the single method and turn off every other lazy-load option: in the theme, the page builder, the image optimisation plugin and the caching plugin. On WordPress, core already adds the attribute, so a plugin is optional. Clear the page cache and any CDN cache after changing settings. Confirm in view source that images now have a real src and loading="lazy", and in the Network panel that nothing loads twice.
Step 5
Make the first screen eager
Find every image and iframe visible on first load, including the logo and the hero. Remove loading="lazy" from them, and add fetchpriority="high" to the main image only. Most plugins have an option to exclude the first images or images with a certain class. Run PageSpeed Insights and confirm the warning about a lazily loaded LCP image has gone, and check in the Network panel that the hero is one of the first images requested.
Step 6
Add width and height
Give every lazy image a width and height that match its real proportions, or set aspect-ratio in CSS. Without them, the browser does not know how much space to keep, and the page jumps when each lazy image arrives. The values set the shape, not the displayed size, so responsive CSS still works. Reload and scroll the page, and confirm that text does not move as images appear. Check the layout shift result in PageSpeed Insights too.
Step 7
Handle background images separately
List any large images set with background-image in CSS, since the attribute cannot affect them. For decorative backgrounds below the fold, apply the background with a class added by an IntersectionObserver when the element comes near the viewport. For backgrounds that carry meaning, change them to img elements with loading="lazy" and suitable alt text. Confirm in the Network panel that those files now load only as you scroll towards them.
Step 8
Retest across browsers and templates
Repeat the Network panel scroll test on your longest page, a product or service page and the home page, because templates load images differently. Check the same pages on a phone and in Safari or Firefox if your visitors use them. Expect images within a screen or two of the viewport to load straight away. Finally, run PageSpeed Insights on mobile to confirm total page weight has fallen and LCP has not got worse.
Edge cases worth knowing
- Printing and saving. Some browsers load all lazy images before printing, but not every one does reliably. If visitors print long pages, test it.
- Anchor links. Jumping to a section far down the page can show images still loading for a moment, because they only start once they come within range.
- Search engines. Google's crawler renders pages with a tall viewport and loads native lazy images it finds in the HTML. Script-based lazy loading that waits for scrolling may hide images from crawlers, which is another reason to prefer the native attribute.
- Tiny pages. On a page with only a few images, lazy loading saves little because everything is within the loading distance anyway.
- Images in picture elements. Put
loading="lazy"on the innerimg, not on thesourceelements.
Keeping it working
Keep one lazy-loading method on the site and write down which one it is, so a new plugin or theme does not quietly add a second. After installing an optimisation plugin or changing theme, view the source of a long page and check the first image is eager and the rest carry the native attribute with real src values.
Every few months, run PageSpeed Insights and look for the audit warning that the LCP image was lazily loaded, and repeat the Network panel scroll test on your longest page. For a step-by-step testing method, see how to check lazy loading is working.
1. Why are my lazy images loading straight away?
Lazy images often load straight away because they are within the distance the browser uses to start fetching them before they scroll into view. Browsers deliberately load lazy images early so they are ready when the visitor reaches them, and the distance is larger on slow connections. On a short page that can include every image. If every image on a long page loads at once, look for a lazy-load script copying data-src into src, or for the attribute being added by JavaScript.
2. Does loading lazy work on background images?
No, the loading attribute only works on img and iframe elements, so it has no effect on images set as CSS backgrounds. A background image loads when the browser applies the CSS rule to an element in the layout. To delay a background below the fold, add the class that sets it with an IntersectionObserver once the element approaches the viewport, or change the image to an img element with the lazy attribute if it suits the design.
3. Should I use a lazy-load plugin or native lazy loading?
For most sites native lazy loading is the better choice, because it is built into all current major browsers, needs no script and keeps real image addresses visible to search engines. Script-based plugins were needed before browsers supported the attribute, and they can still help with special cases such as background images. Do not run both at once, as that can cause images to load twice or not at all. On WordPress, core adds native lazy loading automatically.
4. Can lazy loading hurt my LCP?
Yes, lazy loading hurts LCP when it is applied to the main image or anything else visible on first load. The browser delays fetching lazy images until it knows where they sit on the page, so a lazy hero starts downloading later than it should. PageSpeed Insights warns about this. Remove the lazy attribute from images on the first screen and add fetchpriority high to the main image, while keeping lazy loading for images further down.
5. Does Google index lazy-loaded images?
Google can index images that use native lazy loading, because its crawler renders the page with a tall viewport and fetches images found in the HTML. Problems arise with script-based lazy loading that keeps the real address only in a data attribute and waits for scroll events, since the crawler does not scroll like a person. Use the native loading attribute with the real address in src, and check rendered pages with the URL Inspection tool in Google Search Console.
6. Does loading lazy work on iframes?
Yes, loading lazy works on iframes in all current major browsers, although support arrived later than for images, so very old browser versions load iframes normally. It is especially useful for video embeds and maps, which can pull in large amounts of script. Add the attribute along with a width, height and descriptive title. As with images, do not lazy-load an iframe that is visible when the page first loads, or it will appear later than it should.
7. Why do some lazy images never appear?
Lazy images that never appear are usually caused by a lazy-load script that failed to run, leaving src empty or pointing at a placeholder while the real address sits in data-src. A JavaScript error elsewhere on the page, a script deferred too far or a blocked file can all stop it. Check the Console for errors and the page source for data-src attributes. Switching to native lazy loading with the real address in src removes this risk entirely.
The image optimisation checker on aiwebpageseo.com flags lazy-loaded hero images, missing dimensions and oversized files on any page, so you can see which images need changing.
Open Image Optimisation Fixes โ