# Image lazy loading implemented for below-fold content

- **Pattern:** `ab-002006` (`performance-core.image-media-optimization.lazy-loading`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002006
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002006)

## Why it matters

Eager-loading all images transfers bytes the user may never scroll to see (ISO-25010 time-behaviour). A product listing page with eight gallery images below the fold can waste 450 KB on initial load — bytes that delay the LCP image, consume mobile data allowances, and increase time-to-interactive on slow connections. The browser's native `loading="lazy"` attribute costs one attribute; the performance payoff is the elimination of all below-fold image requests until the images actually enter the viewport.

## Severity rationale

Medium because loading below-fold images eagerly wastes bandwidth and delays initial render, with measurable impact on mobile users and slow connections.

## Remediation

Mark all below-fold images with `loading="lazy"`. Above-fold images must use `loading="eager"` (or omit the attribute — eager is the default):

```html
<!-- Above fold: load eagerly -->
<img src="/hero.jpg" alt="Hero" loading="eager" />

<!-- Below fold: defer until near viewport -->
<img src="/product-1.jpg" alt="Product 1" loading="lazy" />
```

With Next.js, `loading="lazy"` is the default for `next/image`; the LCP image needs `priority` to override it:
```tsx
// src/app/page.tsx
<Image src="/hero.jpg" alt="Hero" priority width={1200} height={600} />
<Image src="/product.jpg" alt="Product" width={300} height={300} /> {/* lazy by default */}
```

Do not mark the hero or any above-fold image as `loading="lazy"` — that delays the LCP element and directly degrades Core Web Vitals.

## Detection

- **ID:** `lazy-loading`
- **Severity:** `medium`
- **What to look for:** Count all relevant instances and enumerate each. Scan image and iframe elements for `loading="lazy"` attribute or Intersection Observer patterns. Check whether below-fold images are loaded eagerly (synchronously) or deferred. Look for lazy loading libraries in dependencies (react-lazyload, vanilla-lazyload, etc.) or custom implementations.
- **Pass criteria:** Below-fold images (not in initial viewport) use `loading="lazy"` attribute natively, or are lazy-loaded via Intersection Observer or a library. At least 1 implementation must be verified. Above-fold images use `loading="eager"` or omit the attribute (default eager).
- **Fail criteria:** All images load eagerly including those far below fold, or lazy loading is not implemented. Large below-fold images cause unnecessary initial page load delay.
- **Skip (N/A) when:** The project has no images or all images are above the fold on typical viewports.
- **Detail on fail:** Specify the below-fold images. Example: `"Product gallery images (8 images, 450KB total) all loaded eagerly. Images 500px below fold start loading immediately instead of on-demand"` or `"Hero image at 100px marked with loading='lazy' incorrectly; should be eager"`.
- **Remediation:** Lazy loading defers image loading until the image enters the viewport, reducing initial page load:

  Use native `loading="lazy"`:
  ```html
  <img src="product.jpg" alt="Product" loading="lazy" />
  ```

  Or with Next.js Image:
  ```tsx
  <Image src="/product.jpg" alt="Product" loading="lazy" width={300} height={300} />
  ```

  For more control, use Intersection Observer:
  ```tsx
  const [isVisible, setIsVisible] = useState(false)
  const ref = useRef()
  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) setIsVisible(true)
    })
    observer.observe(ref.current)
  }, [])
  return isVisible ? <img src="..." /> : <div ref={ref} />
  ```

## External references

- iso-25010 performance-efficiency.time-behaviour

Taxons: performance

HTML version: https://auditbuffet.com/patterns/ab-002006
