# Lazy loading images: what to lazy-load and what never to

Lazy loading images cuts bytes without hurting speed. The rule is simple: never lazy-load anything above the fold, always lazy-load the rest. Here is how to do it with images, iframes and backgrounds, and how to avoid the hero image trap that ruins LCP.

Updated 2026-09-14 · Source: https://porteur.ai/guides/lazy-loading-images

## What to lazy-load and what never to

Lazy-load every image that starts below the fold. Never lazy-load anything visible at first paint.

- Do not lazy-load your hero image on "/" or "/pricing".
- Do lazy-load thumbnails below the first viewport on "/blog".
- Do lazy-load product gallery images that sit under the fold on "/product/widget".
- Do not lazy-load the logo, nav icons or any image in the header.
- Do lazy-load user avatars that appear down the page on "/community".

This rule protects Largest Contentful Paint. LCP counts the largest image, video poster, background image or text in the first viewport. A lazy hero delays it.

A fixed page looks like this: the hero image on "/" has no lazy flag and uses fetchpriority=high. Every image below the fold has loading=lazy.

## Native loading: how browsers handle it

Use the native attribute on images and iframes. Browsers support it across the board as of 2026. They start loading a little before the viewport, not exactly at the edge.

```html
<img src="/images/guide-card.jpg" alt="Guide card" loading="lazy" width="640" height="360">
```

> The pixel dimensions in code samples are typical, not measured on your site.

Do not overthink the threshold. The browser has more context than you. It knows the connection, device and scroll speed. Let it decide when to fetch.

Always set width and height or aspect-ratio. That prevents layout shifts when the image arrives. CLS should stay under 0.1 for a good score.

Add decoding=async on images that do not block the first paint. The browser can decode them off the critical path.

```html
<img src="/images/review-avatars.jpg" alt="Customer avatars" loading="lazy" decoding="async" width="320" height="320">
```

> Rule: never use loading=lazy on anything you expect to be the LCP element.

## Avoid the LCP trap: the hero image

A lazy hero image ruins LCP. The browser will not fetch it early, so the largest element renders late. Keep LCP under 2.5 s for a good field score.

1. **Remove lazy on the hero** On "/" replace loading="lazy" with nothing. Keep width and height.
2. **Give it priority** Add fetchpriority=high. This hints the browser to fetch it sooner.
3. **Use responsive sources** Serve WebP or AVIF with srcset and sizes so phones do not download desktop sizes.
4. **Preload when it is CSS** If the hero is a background-image, preload the file. Then keep it out of lazy logic.
5. **Render the HTML server-side** Name the hero image in the HTML. Do not insert it later with JavaScript.

```html
<!-- Good hero on / -->
<link rel="preload" as="image" href="/images/hero-1280.webp" imagesrcset="/images/hero-640.webp 640w, /images/hero-1280.webp 1280w" imagesizes="100vw">
<img src="/images/hero-1280.webp"
     srcset="/images/hero-640.webp 640w, /images/hero-1280.webp 1280w"
     sizes="100vw"
     alt="Plan your roadmap"
     width="1280" height="720"
     fetchpriority="high">

```

A fixed page shows LCP as that hero image, fetched with high priority, not lazy. Other images on "/" below the fold keep loading=lazy and decoding=async.

## Iframes: embeds, videos and maps

Lazy-load iframes that appear below the fold. The native attribute works here too. Embeds are heavy, so this saves a lot of work on first paint.

```html
<!-- YouTube embed below the fold on /guides/getting-started -->
<iframe
  loading="lazy"
  width="560" height="315"
  src="https://www.youtube.com/embed/VIDEO_ID"
  title="Product demo"
  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
  referrerpolicy="strict-origin-when-cross-origin"
  allowfullscreen>
</iframe>
```

Above the fold, replace the iframe with a static facade and load the iframe on interaction. This reduces third-party script cost and improves INP and TBT in lab runs.

```html
<!-- Facade pattern for a hero video on /pricing -->
<button id="play-demo" aria-label="Play demo">
  <img src="/images/demo-poster.webp" width="800" height="450" alt="Watch the demo">
  <span>Play</span>
</button>
<script type="module">
  document.getElementById('play-demo').addEventListener('click', () => {
    const iframe = document.createElement('iframe');
    iframe.width = 800; iframe.height = 450;
    iframe.src = 'https://www.youtube.com/embed/VIDEO_ID?autoplay=1';
    iframe.title = 'Product demo';
    iframe.allow = 'autoplay; encrypted-media; picture-in-picture';
    iframe.referrerPolicy = 'strict-origin-when-cross-origin';
    document.getElementById('play-demo').replaceWith(iframe);
  });
</script>
```

A fixed page uses loading=lazy on a below-the-fold map iframe on "/contact". The hero has a static image until you click Play. INP stays under 200 ms for clicks that do not mount the iframe.

## Background images and JavaScript-inserted content

CSS backgrounds do not support loading=lazy. The browser must discover them from CSS, which often happens late. If the image is below the fold, load it with IntersectionObserver.

```html
<!-- Mark elements that need a background only when near viewport -->
<section class="feature" data-bg="/images/charts.webp"></section>

<style>
.feature { min-height: 480px; background-color: #f6f7f9; }
.feature.bg-loaded { background-image: url('/images/charts.webp'); background-size: cover; background-position: center; }
</style>

<script type="module">
  const io = new IntersectionObserver(entries => {
    for (const e of entries) if (e.isIntersecting) {
      const url = e.target.dataset.bg;
      if (url) {
        const img = new Image();
        img.decoding = 'async';
        img.src = url;
        img.onload = () => e.target.classList.add('bg-loaded');
        e.target.removeAttribute('data-bg');
      }
      io.unobserve(e.target);
    }
  }, { rootMargin: '200px 0px' });

  document.querySelectorAll('[data-bg]').forEach(el => io.observe(el));
</script>
```

For content inserted by JavaScript, the same pattern applies. Use IntersectionObserver to load and mount when near the viewport. Avoid scroll handlers for this job.

> Google indexes lazy-loaded content when it loads without a user action. IntersectionObserver is fine. A scroll event handler that requires scrolling is not.

A fixed page moves a below-the-fold hero variant that used background-image to a proper img with preload if it was above the fold. This protects LCP and keeps CLS low with width and height in HTML.

## How Google sees lazy-loaded content

As of 2026, Googlebot uses a smartphone crawler. It can index lazy-loaded images and content that appear without a click or gesture. IntersectionObserver is supported by Googlebot rendering.

- Content that loads as it nears the viewport is fine.
- Content that needs a true user action is not indexed until it exists in the DOM.
- A scroll handler that only loads on scroll may not run in rendering. Use IntersectionObserver instead.
- Server-rendered HTML wins: include the key content in the document.

Check your pages with PageSpeed Insights and the URL Inspection tool. If an image is missing in the rendered HTML, Google may not index it. Do not gate it on scroll events or clicks.

## Prevent layout shifts when images load

Lazy-loading often exposes weak layout. Images arrive late and push content down. Reserve space so the layout does not jump. Aim for CLS under 0.1.

- Set width and height on every img. Modern browsers use them to set the aspect ratio.
- If you cannot, set aspect-ratio in CSS on the image box.
- Give ad and embed slots a min-height so banners and iframes do not push content.
- Avoid loading bars that insert above existing content. Use overlays for notices.
- Do not animate layout properties. Use transform for motion.

```html
<!-- Thumbnails on /blog, space reserved -->
<img
  src="/images/thumb-article.webp"
  alt="How we shipped faster"
  loading="lazy" decoding="async"
  width="384" height="216">

```

A fixed page keeps the grid stable when images arrive. The scroll feels calm, and the field CLS assessment passes on mobile and desktop in Search Console.

## Measure the effect: field and lab data

Judge success with field data first. The Core Web Vitals assessment uses the 75th percentile of real Chrome users over 28 days. A URL passes when LCP, CLS and INP are all good.

- Good LCP is up to 2.5 s, poor above 4 s.
- Good CLS is up to 0.1, poor above 0.25.
- Good INP is up to 200 ms, poor above 500 ms.

Use PageSpeed Insights. The top section shows field data if there is enough traffic. That is what Google Search uses for the ranking signal, with content and relevance well ahead of it.

Use the lower section for lab checks. Lighthouse simulates a mid-range phone on throttled 4G. Scores vary between runs. Total Blocking Time weighs 30 percent there and is the proxy for INP in lab tests.

Expect the two to disagree. Field data wins. If a page has too few visits, the reports fall back to origin-level or show nothing. Keep testing as traffic grows.

## A simple lazy loading example that works

Here is a full pattern for images below the fold on "/blog". It uses srcset, sizes, lazy loading and decoding=async, and reserves space with width and height.

```html
<article class="post-card">
  <a href="/blog/how-we-ship-faster">
    <img
      src="/images/thumb-ship-640.webp"
      srcset="/images/thumb-ship-320.webp 320w, /images/thumb-ship-640.webp 640w, /images/thumb-ship-960.webp 960w"
      sizes="(max-width: 640px) 100vw, 384px"
      alt="How we ship faster"
      width="384" height="216"
      loading="lazy"
      decoding="async">
    <h2>How we ship faster</h2>
    <p>What we cut to release weekly.</p>
  </a>
</article>
```

Use the same pattern in product grids. Keep the primary product image above the fold eager, with fetchpriority=high, and lazy-load the rest of the gallery below the fold on the PDP.

## Common mistakes and how to fix them

- Lazy-loading the hero image. Fix: remove loading=lazy, add fetchpriority=high, and preload when it is a CSS background.
- Relying on scroll events to load content. Fix: use IntersectionObserver so Google can see it and it loads near the viewport.
- No width and height on images. Fix: set them or use aspect-ratio to stop layout shifts.
- Lazy-loading icons and logos in the header. Fix: keep critical UI eager and small. Sprite or inline tiny SVGs.
- Huge background images discovered late. Fix: preload above-the-fold backgrounds, or switch to <img> where you can.
- JavaScript inserts the image after hydration. Fix: server-render the <img> in HTML so the browser can fetch it sooner.
- Too many third-party embeds above the fold. Fix: use a facade, lazy-load the iframe, and load scripts on interaction.
- Overusing lazy on short pages. Fix: if your page fits one screen, lazy adds no value and can delay paint.

After fixes, your homepage LCP should be your hero image, fetched early, and your long pages should save requests until the user scrolls. Scrolling feels smooth and inputs stay snappy.

## Questions

### What is lazy loading of images?

It means deferring image requests that start below the fold until the user is likely to see them. You add loading=lazy on those images. The browser fetches them a little before they enter the viewport so they are ready in time.

### What is a lazy loading example?

A blog list on "/blog" where only the first card is above the fold. Keep that first thumbnail eager and mark the rest with loading=lazy, decoding=async, width and height. The browser will fetch them as you scroll down.

### Why are my images loading so slow?

Common causes are a lazy hero image, oversized files, no responsive srcset, a distant server, or discovery through CSS or JavaScript after load. Fix the hero first, serve WebP or AVIF with srcset and sizes, use a CDN, and put key images in the HTML.

### How can I lazy-load images in HTML?

Add loading=lazy on img elements that start below the fold and keep width and height. Pair it with decoding=async. For background images, use IntersectionObserver to set the background when the section nears view. Never lazy-load the hero.

### Does lazy loading affect SEO?

It helps page speed, which supports the Core Web Vitals assessment. Google can index lazy-loaded content that appears without a user action. Avoid scroll-only handlers or clicks to load, because Google may not trigger them in rendering.

### Should I lazy-load iframes and background images?

Yes for iframes below the fold, with loading=lazy or a facade that loads on interaction. Background images need JavaScript: use IntersectionObserver to set them when near view. Do not lazy-load above-the-fold assets.

## Read next

- [Image optimisation for page speed: the hero image and everything below it](https://porteur.ai/guides/image-optimisation-for-page-speed): Fix the hero image first for LCP, then lazy-load the rest. Choose AVIF or WebP, set srcset and sizes, and add width and height to stop shift.
- [Largest Contentful Paint: what slows it and what fixes it](https://porteur.ai/guides/largest-contentful-paint): See what usually counts as LCP, what makes it slow, how to fix it in order, and how to read it in Lighthouse and field data.
- [Core Web Vitals for a product site: the three numbers](https://porteur.ai/guides/core-web-vitals): The product founder’s guide to LCP, CLS and INP: what Google measures, why phones decide the pass, common causes, and how to test and fix.
- [How to read PageSpeed Insights: field data first, then the score](https://porteur.ai/guides/how-to-read-pagespeed-insights): Start with field data, not the score. Learn what Google uses, why scores vary, and how to turn a PageSpeed Insights run into a clear order of fixes.
- [Render-blocking resources: what blocks the first paint and how to unblock it](https://porteur.ai/guides/render-blocking-resources): Stop CSS and synchronous scripts from blocking first paint. Read the Lighthouse audit and fix in order: defer, inline critical CSS, split CSS, and handle fonts.
- [Third-party scripts: the page speed you gave away](https://porteur.ai/guides/third-party-scripts-and-page-speed): See what each third-party script costs and what to do: remove, load on interaction, defer, self-host, facade video and maps, and preconnect smartly.
- [JavaScript SEO](https://porteur.ai/glossary/javascript-seo): What JavaScript SEO means, how Googlebot renders, what breaks, how to fix links and content, and how to choose SSR or SSG so pages index fast.

Paste your homepage URL to see which images to lazy-load, which to keep eager and where LCP is delayed, in about thirty seconds, free, with three findings you can ship. Free check: https://porteur.ai/
