Image optimisation
Format, sizing, priority and lazy loading — with the decisions that matter on mid-range Android handsets and metered connections.
Images are usually the largest thing a page downloads and almost always the LCP element. They are also where the easiest wins live: most sites are shipping images two to four times larger than the box they are rendered into, in a format a decade old, at a priority the browser guessed.
Format: AVIF, WebP, and when neither
| Format | Use for | Watch out for |
|---|---|---|
| AVIF | Photographs and hero images — typically 30–50% smaller than WebP at equal quality | Slower to encode; decode cost is real on old devices, so do not use it for dozens of thumbnails |
| WebP | The safe default for photographic content and the fallback below AVIF | Lossless WebP is often larger than PNG for flat graphics |
| SVG | Logos, icons, diagrams, anything geometric | Sanitise any SVG you did not author — it is executable markup |
| PNG | Screenshots with text, images needing exact pixels | Run it through a lossy quantiser; a 24-bit PNG of a UI screenshot is nearly always wasteful |
| JPEG | Fallback only | Progressive JPEG still beats baseline for perceived speed on slow links |
Serve the size the box actually needs
A 2400px-wide image in a 400px column wastes roughly 96% of the bytes it transferred, and on a device with a 2× screen you still only needed 800px. The browser will choose correctly if you tell it both what widths exist and how wide the box will be — and the second half is the one teams forget.
<img
src="/case-800.avif"
srcset="/case-400.avif 400w, /case-800.avif 800w, /case-1600.avif 1600w"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
width="1600" height="900"
alt="Gate scan in progress at the assembly entrance"
loading="lazy" decoding="async">- Always set width and height (or aspect-ratio). It costs nothing and removes the layout shift entirely.
- Generate three or four candidate widths, not eight. Past a point the extra variants cost build time and cache entries for no measurable benefit.
- Cap the largest variant at what your layout can actually display. A 3000px variant on a page whose content column maxes at 1280 is dead weight in the cache.
Priority: the LCP image must not be lazy
The most common self-inflicted LCP problem we find is loading="lazy" on the hero image, applied by a global rule or a component default. The browser then deliberately delays the one image the metric is measuring.
Delivery and caching
- Put a content hash in the filename and serve with a one-year immutable cache. Revalidation requests on images are pure latency for no benefit.
- Resize on upload, not on request. On-the-fly transformation is convenient and turns every cold cache entry into a slow response.
- Strip metadata. Camera EXIF and colour profiles can be a surprising share of a small image.
- Respect Save-Data. When the header is present, serving a smaller variant is a real courtesy on a metered connection — and it is one line in a delivery rule.