Back to Performance
Performance
medium
mid

How do you optimize images in a large web application?

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.

7 min read·~12 min to think through

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:

html
<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 (or aspect-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.

Senior engineer discussion

Seniors describe a pipeline, not per-image tweaks: an image service + CDN doing format/size negotiation on demand, `<img>` with srcset/sizes/lazy/dimensions, and the LCP image explicitly prioritized (eager + fetchpriority + preload). They tie each technique to the metric it moves (LCP, CLS, total bytes) and add RUM monitoring to catch regressions.

Related questions