Lazy loading: when it helps and when it hurts
Lazy loading delays images and embeds until they are needed. How to use native lazy loading safely, what never to lazy-load, and the mistakes to avoid.
In this guide
What lazy loading does
A page can have dozens of images, and most are far below the visible part. Downloading all of them at the start competes with the files the visitor needs first: the main image, the styles, the scripts. Lazy loading postpones the download of the ones that are not near the screen, until the visitor scrolls close to them. The page loads sooner and transfers less data, and a visitor who never scrolls to the bottom never pays for it.
The browser has a built-in way to do this. Add the loading attribute to an image or an iframe:
<img src="/img/team.avif" width="800" height="600" alt="The team" loading="lazy">
<iframe src="https://example.com/map" title="Our location" loading="lazy"></iframe>For images it works in Chrome 77 and later, Firefox 75 and later and Safari 15.4 and later. For iframes it works in Chrome 77, Firefox 121 and Safari 16.4 and later. A browser that does not know the attribute ignores it and loads the image normally, so there is nothing to break.
The browser decides how close to the screen "near" is. You cannot set a distance with the attribute, and it differs between browsers and connections.
What to lazy-load
Lazy-load anything that is not visible when the page first appears:
- images further down the page, such as a gallery, a team section or the images in a long article;
- iframes for videos, maps and social posts below the first screen;
- images in tabs, accordions and carousel slides that are not showing;
- the logos in a footer strip.
What never to lazy-load
The main image at the top. It is usually the element that decides your LCP. With loading="lazy", the browser waits to find out whether the image is near the screen, which delays exactly the thing the page is waiting for. In the 2025 Web Almanac, about 16% of pages lazy-loaded their LCP image. Lighthouse 13's "LCP request discovery" insight flags it with the message that LCP resources should not use loading=lazy. See how to fix a slow LCP.
Anything else in the first screen. Images that are visible on load, such as a logo, the first row of a product grid, or the first slide of a carousel, should load normally. A good rule is the first screen on a phone: everything you can see without scrolling is eager.
Images that need to be ready at the moment of use. A menu's images, or a lightbox's first picture, that appear on a click will arrive late if they have not loaded. Preload or load them eagerly if the delay would be noticed.
Always set width and height
A lazy image that has not loaded yet has no size to the browser, unless you give it one. Without width and height, it takes no space; the visitor scrolls, the image arrives and the content below jumps. MDN notes that explicit dimensions are recommended for all images, and especially for lazy-loaded ones, for this reason. The attributes also stop the problem in the other direction: an image with zero height would never be seen to cross into the viewport by some scripts. See layout shift and how to fix it.
What WordPress does for you
WordPress adds loading="lazy" to images and iframes by itself, with some care. It leaves the first few images in the content without it (three by default, since version 6.3), skips images in the header, and since version 6.3 gives the first large image fetchpriority="high". The checks depend on images having width and height attributes. Themes, page builders and lazy-loading plugins can override any of this, and often do. If your main image has loading="lazy", a plugin or theme setting is the cause. Look for an option to exclude the first image from lazy loading.
Methods that are not the native attribute
Some sites use JavaScript to lazy-load. The old pattern puts the real address in data-src, and a script copies it to src when the image nears the screen. The newer pattern uses the browser's IntersectionObserver:
const io = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (!entry.isIntersecting) continue;
const img = entry.target;
img.src = img.dataset.src;
io.unobserve(img);
}
}, { rootMargin: '400px' });
document.querySelectorAll('img[data-src]').forEach((img) => io.observe(img));The rootMargin starts the load a little before the image comes into view. IntersectionObserver is supported in all current browsers. Use script-based loading only where the attribute cannot do the job: for CSS background images, for components that build themselves with JavaScript, or for heavy widgets. Script-based methods have three drawbacks: the image is invisible to the browser until the script runs, the images do not load at all if the script fails or is blocked, and there is more code to maintain. The Web Almanac found that custom lazy loading of the LCP image (the data-src method) was on about 6% of mobile pages, and native lazy loading of it on about 10%.
For background images, set the image with a class that you add when the element nears the screen. For video, use a small poster image and preload="none", so that nothing downloads until the visitor presses play.
Mistakes that slow pages down
- Lazy-loading everything, including the main image. The most common mistake. Many plugins have an "all images" mode.
- No dimensions. The page jumps as images arrive.
- A placeholder that is not the same shape. A grey box of the wrong size shifts the page when the real image replaces it.
- Lazy-loading images in a carousel's first slide, or in the first row of a grid.
- Scripts that require scrolling to start. A lazy loader that waits for a scroll event never loads images for a visitor on a tall screen who has not scrolled yet.
- Forgetting the `load` event. MDN points out that lazy images inside the viewport may not have loaded when the
loadevent fires, because lazy images do not count toward it. Scripts and testing tools that wait forloadmay measure too early. - Lazy-loading tiny images. A 2 KB icon does not benefit. The saving is in large files.
How to check
- View the HTML of your main image: it should not have
loading="lazy". - Throttle the network in Chrome DevTools, reload, and watch the Network panel's image filter while you scroll. Images below the fold should appear only as you approach them.
- Read the Lighthouse report. The "LCP request discovery" insight warns when the LCP image is lazy.
- Check for layout shift while scrolling, which the lab test may not see. Scroll slowly through the whole page.
Check your own page
Run the free audit and look at how the main image is loaded. If it is lazy, remove the attribute from that image first. Then scroll the page and look for anything that jumps when images arrive, and add the missing sizes.
Sources
- Lazy loading on MDN: the
loadingattribute, code splitting andIntersectionObserver. - The img element on MDN:
loading,width,heightand why dimensions matter for lazy images. - Performance chapter of the 2025 Web Almanac: how many pages lazy-load their LCP image.
- WordPress core: media.php: when WordPress adds
loading="lazy".
Start here
Free website audit
Live Google data for any public page: mobile and desktop.
Mobile + desktop · Lighthouse lab test · Real-user Chrome data when Google has it
Rather have this done for you? Speed optimization · Book a call