Image Optimization
🖼️ Image Optimization
Modern Formats: WebP and AVIF
WebP is typically 25–35% smaller than an equivalent-quality JPEG; AVIF is often smaller still. Both have broad modern browser support, but a fallback chain via <picture> keeps older browsers working:
Format Fallback With <picture>
The browser tries each <source> in order, falling back to the plain <img> if nothing else is supported — note the width/height on the fallback <img>, still doing Chapter 3's CLS-prevention work regardless of which format actually loads.
Responsive Images: srcset and sizes
A phone shouldn't download a desktop-sized image it'll never display at full resolution — srcset offers multiple resolutions, and sizes tells the browser how much viewport width the image will actually occupy, letting it pick the right candidate before layout even happens:
🐢 Lazy Loading
Native loading="lazy"
Simplest approach — the browser itself defers offscreen images until they're about to scroll into view, reducing bandwidth competition during initial load with zero JS cost.
IntersectionObserver-Based
More control — custom thresholds, placeholder swap-in animations — but adds JS running on the main thread (Chapter 4's concern), needed mainly for advanced behavior the native attribute doesn't offer.
Image CDNs
An image CDN resizes, converts formats, and compresses on the fly based on URL parameters — often negotiating format automatically via the browser's Accept header (AVIF to browsers that support it, WebP otherwise, JPEG as a final fallback), offloading this entire pipeline from the origin server.
Unoptimized vs Fully Optimized
Single-Size JPEG
One large JPEG served to every device regardless of screen size — wasted bytes on mobile, no format negotiation, no lazy loading.
Responsive AVIF/WebP
Right format, right resolution, right loading strategy for above vs below the fold — meaningfully lighter and faster across every device.
💻 Coding Challenges
Challenge 1: Write a Responsive <picture>
Write a <picture> element for a below-the-fold product image, with AVIF and WebP sources, a JPEG fallback with dimensions, and native lazy loading applied correctly.
Goal: Practice combining format fallback, dimensions, and lazy loading in one correct element.
Challenge 2: Choose Native vs IntersectionObserver
A team needs offscreen images to fade in smoothly with a custom animation as they load, well before native loading="lazy"'s default threshold. Recommend an approach and justify it using this chapter's tradeoffs.
Goal: Practice recognizing when the simpler native option genuinely isn't sufficient.
Challenge 3: Write srcset/sizes for a Two-Column Layout
An image occupies 100% of viewport width on screens under 700px, and 50% of viewport width otherwise. Write the sizes attribute for this layout, and three reasonable srcset candidates.
Goal: Practice writing an accurate sizes value that actually matches a real layout.
Applying loading="lazy" to an above-the-fold hero image directly contradicts Chapter 2's guidance — the LCP element needs to be discovered and fetched as early as possible, ideally preloaded. Native lazy loading works by deferring a fetch until the browser judges an image is about to scroll into view; for an image that's already visible on load, this actively delays it, often making LCP significantly worse. Never apply loading="lazy" to the LCP candidate or anything else visible above the fold — reserve it strictly for offscreen, below-the-fold images.
🎯 What's Next
With images covered, the next chapter widens the lens: Lazy Loading Beyond Images — code splitting, dynamic imports, and the same above/below-the-fold tradeoff applied to JavaScript and components.