/ โ† Page Speed Fixes / Implement Lazy Loading
In this guide
  1. What lazy loading is in web development
  2. What to lazy load, and what never to
  3. Lazy loading in HTML with loading=lazy
  4. Lazy loading in JavaScript with IntersectionObserver
  5. Platform notes
  6. Worked example
  7. The fix, step by step
  8. Edge cases worth knowing
  9. Keeping lazy loading correct
  10. Questions and answers

What lazy loading is in web development

Lazy loading means delaying the download of a resource until it is likely to be needed. Instead of fetching every image, video and embedded map as soon as the page opens, the browser fetches only what is on or near the screen, and the rest as the visitor scrolls towards it.

The opposite is eager loading, which is what browsers do by default. A long article with forty photos downloads all forty at once, even if most visitors read the first few paragraphs and leave. Lazy loading cuts that waste. The page uses less data, the important first-screen files face less competition for bandwidth, and phones on slow connections show the top of the page sooner.

There are two ways to implement it:

Use the native attribute wherever it works and keep JavaScript for the rest. The native version is simpler, cannot break if a script fails, and is supported in all current major browsers for both images and iframes.

What to lazy load, and what never to

ElementLazy load?Why
Hero image or first-screen bannerNoDelaying it makes Largest Contentful Paint slower
Logo and images in the headerNoVisible straight away, and usually small
Images further down the pageYesMost visitors never scroll to all of them
Product grids beyond the first rowYesLarge saving on category pages
YouTube, maps and other iframes below the foldYesEach embed can pull in hundreds of kilobytes of script
Video filesUse preload and a posterThere is no loading attribute for <video>
Text contentNoSearch engines and readers need it in the HTML

The rule that matters most: nothing in the first screenful should be lazy loaded, on mobile or desktop. Many themes and plugins add loading="lazy" to every image, and the most common lazy loading mistake on real sites is a lazy hero image. The LCP guide explains why that slows the page.

Lazy loading in HTML with loading=lazy

For an image below the fold, add the attribute and always give the width and height, so the browser reserves the space before the file arrives:

<img src="/images/oven-600.webp"
     srcset="/images/oven-600.webp 600w, /images/oven-1200.webp 1200w"
     sizes="(max-width: 700px) 100vw, 700px"
     width="1200" height="800"
     alt="Bread loaves cooling beside the bakery oven"
     loading="lazy" decoding="async">

For an embed:

<iframe src="https://www.youtube-nocookie.com/embed/VIDEO_ID"
        width="560" height="315" title="How we shape sourdough"
        loading="lazy" allowfullscreen></iframe>

The browser does not wait until the image is exactly on screen. Chrome starts loading lazy images when they are within roughly 1,250 pixels of the viewport on a fast connection and further away on slow connections, so in normal scrolling the image is ready before the reader reaches it.

How the attribute behaves

Lazy loading in JavaScript with IntersectionObserver

When you need lazy loading for something the attribute cannot cover, keep the real address in a data- attribute and copy it across when the element approaches the viewport:

<img data-lazy src="/images/placeholder.svg"
     data-src="/images/team-800.webp" width="800" height="533"
     alt="The Example Bakery team at the counter">
<noscript><img src="/images/team-800.webp" width="800" height="533"
     alt="The Example Bakery team at the counter"></noscript>

<script>
document.addEventListener('DOMContentLoaded', () => {
  const items = document.querySelectorAll('[data-lazy]');
  const show = (el) => {
    if (el.dataset.src) el.src = el.dataset.src;
    if (el.dataset.srcset) el.srcset = el.dataset.srcset;
    if (el.dataset.bg) el.style.backgroundImage = 'url(' + el.dataset.bg + ')';
    el.removeAttribute('data-lazy');
  };
  if (!('IntersectionObserver' in window)) { items.forEach(show); return; }
  const io = new IntersectionObserver((entries) => {
    entries.forEach((e) => {
      if (e.isIntersecting) { show(e.target); io.unobserve(e.target); }
    });
  }, { rootMargin: '300px 0px' });
  items.forEach((el) => io.observe(el));
});
</script>

The rootMargin of 300 pixels starts the download a little before the element is visible. The fallback loads everything at once in the rare browser without the API, and the <noscript> copy covers visitors with JavaScript switched off.

Background images and video

The same script handles a section with a large CSS background: give it a data-lazy and a data-bg attribute, and the style is only set when it comes near the screen. For video, write preload="none" and a small poster image, so nothing but the poster downloads until the visitor presses play.

โš ๏ธ Do not use scroll events to trigger loading. They fire constantly, which costs processing time, and search engine crawlers do not scroll, so content that only appears after a scroll event may never be seen.

Platform notes

Worked example

Example Bakery has a recipe page with a hero photo, twelve step photos and a YouTube video at the bottom. Before the change, all thirteen photos and the video player downloaded on load, about 3.1 MB on a phone.

  1. The hero keeps no loading attribute and gains fetchpriority="high".
  2. The first step photo, which sits just under the hero on a phone, is also left eager.
  3. The other eleven photos get loading="lazy", plus their width and height.
  4. The video iframe gets loading="lazy" and a title.

After the change, the first-screen download drops to about 520 KB, the hero appears sooner, and the remaining photos arrive as the reader scrolls. Layout shift stays at zero because every image has dimensions. You can confirm the pattern with the steps in how to check lazy loading is working.

The fix, step by step

Step 1

List what is in the first screen

Open the page on a phone-sized viewport in Chrome DevTools and note every image, video and embed visible before you scroll, then repeat on a desktop width. These must load eagerly. Everything below that line is a candidate for lazy loading. Write the list down per template, such as home page, blog post and product page, because the first screen usually changes between templates and devices.

Step 2

Remove lazy loading from the first screen

Search the rendered HTML for loading="lazy" on the items in your first-screen list and remove it from the template, theme setting or plugin that adds it. Add fetchpriority="high" to the single main image. Reload with the Network panel open and check that the hero request starts near the top of the waterfall, before images further down the page are requested at all, and that the hero now shows high priority in the Priority column.

Step 3

Add loading=lazy to images below the fold

For each image below the first screen, add loading="lazy" and make sure it has width and height attributes that match its aspect ratio. Keep a real src and srcset on the tag so no script is needed. Reload the page without scrolling and check in the Network panel that the lower images have not been requested, then scroll down slowly and watch each one load just before it reaches the screen.

Step 4

Lazy load iframes and embeds

Add loading="lazy" to YouTube, Vimeo, map and form iframes that sit below the fold, and give each a title that describes it for screen reader users. For a heavy embed near the top of the page, consider a facade: a static image with a play button that swaps in the real player when clicked. Confirm in the Network panel that the embed's scripts only load once you scroll down.

Step 5

Use IntersectionObserver for the rest

For CSS background images, custom widgets or content that the attribute cannot cover, move the address into a data-src or data-bg attribute and use an IntersectionObserver with a rootMargin of a few hundred pixels to set it when the element nears the viewport. Add a fallback that loads everything when the API is missing, and a noscript copy for important images. Test by scrolling slowly and quickly.

Step 6

Handle video separately

Video elements have no loading attribute, so set preload="none" and a small, compressed poster image on each one below the fold. For autoplaying background videos, load the source with your observer only when the section comes into view, and pause it when it leaves. Check the Network panel to confirm the video file itself is not downloaded until the visitor plays it or scrolls to it.

Step 7

Check crawlers can see the content

Run the page through the URL Inspection live test in Google Search Console and open the rendered HTML. Every lazy image should have its real address in src, not only in a data attribute, and no content should depend on a scroll event. Also view the page with JavaScript switched off in DevTools settings to confirm that the noscript fallbacks and native lazy images still show.

Step 8

Measure before and after

Run a lab speed test on mobile and compare total page weight, LCP and layout shift with your earlier figures. The first-screen download should be smaller, LCP the same or better, and layout shift unchanged at or near zero. Repeat on each template you changed. If LCP got worse, an image in the first screen is still lazy loaded, so return to the second step and check again.

Edge cases worth knowing

Keeping lazy loading correct

  1. Make the first section of each template eager in the template itself, so editors adding a new hero cannot turn lazy loading back on.
  2. Run one lazy loading method. Overlapping plugin and theme options cause images that never appear.
  3. Check new pages with the URL Inspection live test in Search Console and look at the rendered HTML: lazy images should have a real src.
  4. Keep an eye on LCP and layout shift in your speed checks after each theme or plugin update. The page speed guide covers the other common slowdowns.
Questions and answers

1. What is lazy loading?

Lazy loading is a technique where a web page delays downloading images, videos, iframes or other resources until they are about to come into view, instead of loading everything when the page opens. It saves data for visitors who never scroll to the bottom, reduces the competition for bandwidth while the first screen loads, and helps pages appear faster on slow phone connections. In web development it is done with the HTML loading attribute or with JavaScript, usually through the IntersectionObserver API.

2. How do I implement lazy loading in HTML?

To implement lazy loading in HTML, add loading set to lazy on each img or iframe element that sits below the first screen, and give every image its width and height. That is all the code needed, and all current major browsers support it. The browser then decides when to fetch each file, usually well before the visitor scrolls to it. Leave the attribute off the hero image and anything else visible when the page opens, because those should load straight away.

3. How do I implement lazy loading in JavaScript?

To implement lazy loading in JavaScript, store the real image address in a data attribute, then create an IntersectionObserver that watches those elements and copies the address into src when each one comes within a few hundred pixels of the viewport. Stop observing each element once it has loaded. Add a fallback that loads everything if the API is missing and a noscript copy for key images. Use this for background images, video and widgets, and the native attribute for normal images.

4. Is lazy loading bad for SEO?

Lazy loading is not bad for SEO when it is done correctly, and Google recommends it for images further down long pages. Problems arise when the hero image is lazy loaded, which slows Largest Contentful Paint, or when content only loads after a scroll or click, because Googlebot does not scroll. Native loading set to lazy and IntersectionObserver both work with Google rendering. Check with the URL Inspection live test that the rendered HTML contains your images and text.

5. Should I lazy load images above the fold?

No, you should not lazy load images above the fold. Lazy loading tells the browser to wait until it knows where the image sits on the page, which delays the request for the very images visitors see first. That makes Largest Contentful Paint slower. Load first-screen images normally by leaving the loading attribute off, and give the main image fetchpriority set to high. Check the fold on a phone as well as a desktop, because it falls in a different place.

6. Does lazy loading work for background images?

Native lazy loading does not work for CSS background images, because the loading attribute only applies to img and iframe elements. To lazy load a background, keep the image address in a data attribute on the element and use an IntersectionObserver to set the background style when the section comes near the viewport. Alternatively, where the image carries meaning, replace the background with a normal img element positioned behind the content, which can then use the attribute directly.

7. Why are my lazy loaded images not showing?

Lazy loaded images that never appear usually have their real address only in a data attribute and a script that failed, was deferred too long or only listens for scroll events. Other causes are two lazy loading tools fighting over the same images, images inside hidden containers, and a caching plugin that strips the script. Check the browser console for errors and the rendered HTML for a real src. Switching to the native loading attribute removes most of these failure points.

Check which images on your page load too early or too late

The page speed audit on aiwebpageseo.com shows your LCP element and the images on the page, so you can see at a glance whether the hero is lazy loaded or images further down are loading eagerly.

Open Page Speed Fixes โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All Page Speed Fixes  ·  Loading Lazy Not Working  ·  Check Lazy Loading Is Working  ·  Image Lazy Load Fixes  ·  Slow LCP  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.