Lazy loading with image optimization
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.
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.
<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.
<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.
<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.
<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:
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-colorwhile 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 havealt, 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.