WhollySoftware
Back to blogWeb

Image Optimization at Scale: Formats, CDNs, and Lazy Loading Done Right

Wholly Software TeamFebruary 18, 20256 min read
Image Optimization at Scale: Formats, CDNs, and Lazy Loading Done Right

A real estate listings client came to us with a media library of roughly 40,000 property photos, uploaded over eight years by agents with wildly inconsistent habits — some images were 200KB JPEGs, others were 8MB uncompressed PNGs straight off a DSLR. Re-processing all of them upfront wasn't practical, so we put a Cloudflare Images pipeline in front of the existing S3 bucket that transforms images on request and caches the result, rather than batch-converting the library.

Format selection is automatic based on the Accept header — AVIF where the browser supports it, WebP as a fallback, JPEG for anything older. AVIF gave us roughly 40% smaller file sizes than an equivalent-quality JPEG on photo content, though encoding time is higher, which is exactly why on-demand transformation with caching beats trying to pre-generate every format and size combination for 40,000 images.

Lazy loading needed more care than adding the loading='lazy' attribute and calling it done. Native lazy loading works well but doesn't let you control the trigger distance, and on a listings grid where users scroll fast, images were popping in visibly a beat after entering the viewport. We used an IntersectionObserver with a 200px root margin to start loading images before they're actually visible, which eliminated the visible pop-in on typical scroll speeds.

Responsive sizing through srcset and sizes cut mobile data usage significantly — a hero image that served a single 1600px-wide file to every device now serves a 480px version to phones, calculated from actual layout breakpoints rather than a guess. Getting the sizes attribute right took a few iterations; an early version under-specified it and mobile devices were still requesting the desktop image because the browser's fallback guess was wrong.

The combined effect on the listings grid page: median page weight dropped from 6.2MB to 1.8MB, and LCP for a typical property page went from 3.6s to 1.4s on a 4G connection. None of it required touching a single original file in the S3 bucket, which mattered because re-uploading 40,000 verified property photos was never going to be a serious option.

Web PerformanceImagesCDNCore Web Vitals
Image Optimization at Scale: Formats, CDNs, and Lazy Loading Done Right — Wholly Software