How do you optimize images in a large web app
Serve modern formats (AVIF/WebP), responsive sizes via srcset/sizes, lazy-load offscreen images, prioritize the LCP image, reserve space with width/height or aspect-ratio to prevent CLS, compress and right-size, use a CDN/image service, and consider blur-up placeholders.
Images are usually the largest bytes on a page. Optimizing them at scale means a pipeline, not per-image hand-tuning.
1. Right format
- AVIF — best compression; WebP — great, near-universal support. Fall back to JPEG/PNG via
<picture>. - SVG for icons/logos/illustrations — resolution-independent, tiny.
- Avoid PNG for photos; avoid GIF for animation (use video/WebP/AVIF).
2. Right size — responsive images
Don't ship a 2000px image to a 375px phone:
<img
srcset="img-400.avif 400w, img-800.avif 800w, img-1600.avif 1600w"
sizes="(max-width: 600px) 100vw, 50vw"
src="img-800.avif" alt="..." width="800" height="600" />The browser picks the smallest sufficient file for the viewport and DPR. <picture> for art direction (different crops per breakpoint).
3. Load at the right time
loading="lazy"on offscreen images — don't download what's below the fold.- The LCP image is the exception — it should load eagerly, with
fetchpriority="high", and ideally be preloaded. Never lazy-load your hero image. decoding="async"so decode doesn't block.
4. Prevent layout shift (CLS)
- Always set
width/height(oraspect-ratio) so the browser reserves space before the image loads. This is the #1 image-related CLS fix.
5. Compress
- Strip metadata, tune quality (~75–85 is usually indistinguishable), use modern encoders (MozJPEG, libavif).
- Automate it — a build step or, better, an image service.
6. Use a CDN / image service
At scale, don't pre-generate every variant manually. Cloudinary, imgix, Cloudflare Images, Next.js Image, Vercel/Netlify image optimization do format negotiation, on-the-fly resizing, compression, and CDN delivery from a single source image and URL params. This is the real "large web app" answer.
7. Perceived performance
- Blur-up / LQIP — a tiny blurred placeholder that swaps to the full image (BlurHash, dominant color).
- Skeletons for image-heavy grids.
8. Other
- CDN caching with long
Cache-Control+ content hashing. - Sprites / icon fonts → mostly replaced by inline SVG.
- HTTP/2+ so many images multiplex.
The pipeline mindset
For a large app: one source image → image service generates formats/sizes on demand → CDN delivers → <img> with srcset/sizes/lazy/dimensions → LCP image prioritized. Plus a Lighthouse/RUM check so regressions get caught. Per-image manual optimization doesn't scale; the pipeline does.
Follow-up questions
- •Why should the LCP image never be lazy-loaded?
- •How do width/height attributes prevent layout shift?
- •What does an image CDN / service do that you'd otherwise do by hand?
- •When would you use <picture> instead of srcset?
Common mistakes
- •Lazy-loading the LCP/hero image, delaying the largest paint.
- •Not setting width/height, causing layout shift as images load.
- •Shipping one large image to every device instead of responsive sizes.
- •Serving JPEG/PNG when AVIF/WebP would be far smaller.
- •Manually managing image variants instead of using an image service.
Performance considerations
- •Images dominate page weight and directly affect LCP and CLS. Modern formats cut bytes 30-70%; responsive sizing avoids wasted downloads; lazy-loading defers offscreen cost; dimensions kill layout shift; prioritizing the LCP image speeds the metric that matters most.
Edge cases
- •User-uploaded images of unknown size/format.
- •Retina/high-DPR displays needing larger sources.
- •Animated images — use video instead of GIF.
- •Images behind authentication that can't be CDN-cached publicly.
Real-world examples
- •Next.js <Image> / Cloudinary / imgix doing format negotiation, resizing, and CDN delivery from one source.
- •Blur-up placeholders (BlurHash) on an image-heavy product grid.