Fast wins (do these first)
If you want results quickly, start here:
- Stop overserving: deliver images close to their displayed size.
- Use modern formats: AVIF/WebP where possible.
- Don’t lazy-load the hero image (often your LCP element).
- Reserve space for every image to prevent CLS.
- Cache aggressively for static images.
Choosing formats: AVIF, WebP, JPEG, PNG, SVG
AVIF
Best compression for many photos and gradients. Great when your pipeline supports it. Use WebP/JPEG as fallback if needed.
WebP
Excellent default for modern browsers, often a strong improvement over JPEG/PNG.
JPEG
Still useful as a safe fallback for photos. Use progressive JPEG if your pipeline supports it.
PNG
Use when you need transparency and lossless quality (icons or UI assets). Otherwise, prefer WebP/AVIF.
SVG
Best for logos, icons, and simple illustrations. Keep SVG clean (avoid huge embedded data).
Practical default
- Photos: AVIF → WebP → JPEG
- Icons/illustrations: SVG
- Transparency-heavy UI: WebP/PNG
Sizing correctly (the #1 mistake)
The most common performance mistake: serving a massive image for a small display slot. Fixing sizing usually gives the biggest win.
Rules of thumb
- Never ship a 2000px image to render at 320px.
- For retina screens, target ~2× display size (not 4×).
- Keep hero images tight: large enough to look good, not larger than needed.
Example
360w, 720w, 1080w
Responsive images: srcset + sizes
Responsive images let the browser pick the best file for the user’s screen.
Use srcset to provide multiple sizes and sizes to describe layout.
Example: responsive <img>
<img
src="/images/hero-720.webp"
srcset="
/images/hero-360.webp 360w,
/images/hero-720.webp 720w,
/images/hero-1080.webp 1080w
"
sizes="(max-width: 640px) 360px, (max-width: 1024px) 720px, 1080px"
width="1080"
height="720"
alt="A descriptive hero image"
loading="eager"
decoding="async"
/>
Loading strategy: priority, preload, lazy-load
1) Don’t lazy-load the hero image
If the hero image is your LCP element, lazy-loading delays it and hurts LCP. Load it eagerly and consider preloading.
2) Lazy-load below-the-fold images
Use loading="lazy" for images outside the initial viewport.
3) Use decoding async
decoding="async" can reduce main-thread work while decoding images.
Practical loading rules
- Above the fold: eager (and maybe preload)
- Below the fold: lazy
- All images: decoding async + correct dimensions
Avoid CLS: reserve space
Images cause CLS when the browser doesn’t know their dimensions and the layout shifts as they load. Always reserve space with width/height or a stable aspect ratio.
CLS fixes that work
- Always set
widthandheighton images (or use CSS aspect-ratio). - Reserve space for embeds/iframes and ads.
- Avoid injecting UI above existing content after render.
Caching & delivery
After sizing and formats, caching is the next big win. Static images should be cached long-term.
Practical delivery checklist
- Use fingerprinted filenames for assets you can cache long-term.
- Serve images from a fast origin or CDN when possible.
- Enable compression where relevant (CDNs usually handle this).
Before-you-ship checklist
- ✅ Correct format (AVIF/WebP preferred for photos)
- ✅ Correct size (no overserving)
- ✅ Responsive images (srcset + sizes) for layout-based sizing
- ✅ Hero image not lazy-loaded (LCP-safe)
- ✅ Below-the-fold images lazy-loaded
- ✅ Dimensions reserved (avoid CLS)
- ✅ Caching headers/CDN where appropriate
FAQ
Should I use AVIF or WebP?
Use AVIF when supported in your pipeline, with WebP as fallback. Keep JPEG/PNG for compatibility and special cases.
What’s the biggest image performance mistake?
Overserving: sending images far larger than the display size. Responsive images usually fix this.
Should I lazy-load all images?
Lazy-load below-the-fold images, but do not lazy-load the hero image if it’s the LCP element.
How do images cause CLS?
CLS happens when layout shifts after images load. Reserve space with width/height or a stable aspect ratio.