Images are most of the weight
On a median page, images are the largest share of the bytes and usually the LCP element too. They are also the easiest win in performance, because almost every page ships them two or three times larger than the screen can show.
Three decisions do nearly all the work: the format, the size, and whether the browser knew the shape before the bytes arrived.
Format
<picture>
<source srcset="/hero.avif" type="image/avif">
<source srcset="/hero.webp" type="image/webp">
<img src="/hero.jpg" width="1200" height="600" alt="…" fetchpriority="high">
</picture>
| Format | Typical size vs JPEG | Use it for |
|---|---|---|
| AVIF | ≈ 50–65 % smaller | photographs — best compression, slower to encode |
| WebP | ≈ 30–40 % smaller | the safe default, supported everywhere |
| JPEG | baseline | the fallback |
| PNG | much larger | only when you need lossless or real transparency at sharp edges |
| SVG | tiny | logos, icons, anything geometric — and it never blurs |
Size — let the browser choose
srcset lists what exists and how wide each one is. sizes tells the browser how wide the image will be in CSS pixels at this viewport — it has to know that before layout, which is why you have to say it. The browser then multiplies by the device pixel ratio and takes the smallest candidate that is big enough.
Beyond about 2× the return is invisible. A phone reporting DPR 3 does not need a 1170-wide image for a 390 px slot; cap at 2 and nobody will ever see the difference.
<img
srcset="/hero-400.avif 400w, /hero-800.avif 800w, /hero-1200.avif 1200w, /hero-2000.avif 2000w"
sizes="(max-width: 700px) 100vw, 60vw"
src="/hero-800.avif" width="1200" height="600" alt="…">
Shape — before the bytes
width and height attributes are not sizing; they are an aspect ratio, and CSS height: auto keeps them from fighting your layout. With them the browser reserves the right box immediately and nothing jumps when the image arrives. Without them you have the layout shift from the previous lesson.
img { max-width: 100%; height: auto; } /* the two lines every stylesheet needs */
The rest of the attributes
And one warning: preload every image and you have preloaded nothing. Priority is relative; marking everything high is the same as marking nothing.
| Attribute | Meaning |
|---|---|
loading="lazy" | Do not fetch until it is near the viewport. Below the fold only. |
fetchpriority="high" | Jump the queue. For the LCP image, and for nothing else. |
decoding="async" | Do not block the next paint while decoding |
sizes="auto" | On a lazy image, let the browser use the real laid-out width — it already knows it by then |