Back to Performance
Performance
easy
mid

How do you combine lazy loading with image optimization for fast first paint?

Use `<img loading="lazy">` for below-fold, `fetchpriority="high"` for the LCP image, modern formats (AVIF/WebP) via `<picture>` with fallbacks, correctly-sized `srcset` + `sizes`, explicit `width`/`height` (or aspect-ratio) to prevent CLS, and an image CDN that serves the right variant per device. Prefer `<img>` over CSS `background-image` for content imagery.

6 min read·~10 min to think through

Images are usually 60–80% of a page's bytes. The fix isn't one trick — it's stacking four cheap ones.

1. Lazy load below-fold imagery.

html
<img src="hero.jpg" alt="..." width="800" height="600" loading="eager" fetchpriority="high">
<img src="..." loading="lazy" alt="...">

loading="lazy" is native (no IntersectionObserver wiring needed). Set on below-the-fold images only — putting it on the LCP image delays the largest paint and hurts the score.

2. Mark the LCP image as high-priority.

html
<img src="hero.jpg" fetchpriority="high" loading="eager" ...>
<link rel="preload" as="image" href="hero.jpg" fetchpriority="high">

Without this, the browser may queue the hero behind CSS or font fetches. fetchpriority="high" (Chrome) and a <link rel="preload"> for cross-browser support get it onto the wire ASAP.

3. Responsive images — srcset + sizes.

html
<img
  srcset="
    img-400.jpg  400w,
    img-800.jpg  800w,
    img-1600.jpg 1600w"
  sizes="(max-width: 600px) 100vw,
         (max-width: 1200px) 50vw,
         33vw"
  src="img-800.jpg"
  width="800" height="600"
  alt="...">

The browser picks the right variant given viewport width, DPR, and the sizes hint. A phone never downloads the 1600w version.

4. Modern formats with fallback.

html
<picture>
  <source type="image/avif" srcset="img.avif">
  <source type="image/webp" srcset="img.webp">
  <img src="img.jpg" alt="..." width="800" height="600">
</picture>

AVIF is ~50% smaller than JPEG at equivalent quality; WebP is ~25–30% smaller. <picture> falls through gracefully for older clients.

5. Reserve space (no CLS).

Always set width + height attributes or CSS aspect-ratio. The browser computes the box before the image loads, so content below doesn't jump:

css
img { aspect-ratio: 4 / 3; width: 100%; height: auto; }

6. Use an image CDN. Hand-generating 6 variants × AVIF/WebP/JPG is unmaintainable. next/image, Cloudinary, imgix, Cloudflare Images, etc., serve variants on demand from a single source. They handle DPR, format negotiation via Accept, and quality tuning.

7. Placeholders. Three strategies:

  • Blurhash / LQIP (low-quality image placeholder) — a base64'd tiny version (~30 bytes) inlined as background-image, blurred via CSS. Hides decode latency.
  • Dominant color — single CSS background-color while the image loads.
  • Skeleton — gray rectangle. Fine for grids; less visually integrated.

8. Decode and concurrency hints.

  • decoding="async" — tells the browser to decode off-main-thread.
  • Avoid background-image: url(...) for content imagery. It can't be <img loading="lazy">, can't have alt, can't be in srcset. Background images are for decoration only.

The senior framing — LCP first. The single biggest image-perf win for most pages is making the LCP image load faster. That means: preload it, mark fetchpriority="high", serve AVIF, dimension it correctly so it doesn't shift, and never lazy-load it. Other images can do whatever — they don't move the Core Web Vital.

Measure.

  • <link rel="preload"> shows up in WebPageTest waterfalls — verify the hero image is fetched in the first wave.
  • Chrome → Performance → LCP marker. The LCP element is highlighted — its source URL is what to optimize.

Follow-up questions

  • How does the browser decide which srcset image to download?
  • Why is `background-image` worse than `<img>` for content?
  • When is `loading="lazy"` harmful?
  • What's the trade-off between AVIF and WebP in 2026?

Common mistakes

  • Lazy-loading the LCP image, delaying paint.
  • Missing width/height → CLS.
  • Single huge JPEG served to every device.
  • Using background-image for content (no alt, no lazy, no srcset).

Performance considerations

  • Decode cost matters on mobile; AVIF decodes slower than WebP on some chips — measure on target devices.
  • Total bandwidth on a slow connection is the bottleneck — fewer / smaller images beats any clever technique.
  • Preload only ONE image (the LCP) — preloading many wastes priority.

Edge cases

  • iOS Safari has historically had quirks with very large srcsets — test there.
  • Print stylesheets need the high-res version.
  • CSP `img-src` allow-list — vendor CDNs must be added.

Real-world examples

  • Next.js `<Image>` does all of this automatically — DPR, format negotiation, blur placeholder, sized variants.
  • Pinterest and Unsplash use blurhash-style placeholders for every image.

Related questions