Image Optimisation: The Single Biggest Speed Win on Most Sites
On the average site I audit, images are more than half the page weight — and most of that is avoidable with three changes that take an afternoon.
Before optimising a single query or splitting a JavaScript bundle, look at the images. On most sites they are 60–70% of the bytes, and the fix requires no architectural change at all.
Use a Modern Format
WebP is 25–35% smaller than JPEG at equivalent quality and supported everywhere that matters. AVIF is smaller still. Serve both with a fallback:
<picture>
<source srcset="/hero.avif" type="image/avif">
<source srcset="/hero.webp" type="image/webp">
<img src="/hero.jpg" width="1200" height="630" alt="...">
</picture>
Serve the Size You Actually Display
A 4000px camera image rendered in a 400px card is roughly 100× more data than needed. Resize on upload, and let the browser pick the right one:
<img src="/card-400.webp"
srcset="/card-400.webp 400w, /card-800.webp 800w, /card-1200.webp 1200w"
sizes="(max-width: 700px) 100vw, 33vw"
width="400" height="225" alt="...">
Always Set width and height
Not for sizing — for space reservation. Without them the browser cannot reserve room, and everything below jumps when the image loads. That is your CLS score.
Lazy Load Everything Below the Fold — and Nothing Above It
<img src="..." loading="lazy" decoding="async">
Do not lazy load the hero image. Lazy loading the LCP element delays the exact thing the metric measures.
Preload the Hero
<link rel="preload" as="image" href="/hero.webp">
Resize on Upload, Not on Request
Generate your size variants when a file is uploaded and store them. On-the-fly resizing per request burns CPU on work you will repeat thousands of times.
Do Not Forget the Social Image
Your Open Graph image should be 1200×630 and under about 300KB. Some platforms simply refuse to fetch anything larger, and your shared links end up with no preview at all.