Responsive Images with WebP: srcset, sizes and Getting It Right
Responsive images are one of those web platform features that almost everybody uses and almost nobody has read the specification for. The result is a lot of srcset attributes that look correct, validate fine, and quietly ship the wrong file to half the visitors.
What srcset actually tells the browser
With w descriptors, srcset is a menu: here are the widths I have available. It is not an instruction. The browser combines that menu with the sizes attribute, the device pixel ratio, and sometimes the network conditions, and then picks. You are advising, not commanding.
This is why the same page can serve a different file to two people on identical screens — and why testing on your desktop tells you very little about what a phone receives.
sizes is the attribute that actually matters
If srcset is the menu, sizes is the description of how large the image will be rendered. Get it wrong and every other decision is built on a bad number.
sizes="(max-width: 700px) 100vw, 700px"
This says: below a 700px viewport the image fills the width; above that it renders at a fixed 700px. The browser uses this to work out which entry from the menu to fetch.
The most common error is omitting sizes entirely. The default is 100vw — "this image fills the viewport" — so a thumbnail in a four-column grid will happily download the 1600px variant on a wide screen. This is invisible in testing and expensive at scale.
How many variants do you need?
Fewer than most build configs generate. Three to four widths covers essentially everything: a small variant around 400px, a medium around 800px, a large around 1600px, and optionally 2400px for full-bleed hero images on high-DPI displays.
Generating eight or ten sizes has real costs — build time, storage, cache fragmentation at the CDN — and the marginal benefit past four is negligible, because the browser is choosing the smallest candidate at least as large as it needs, not interpolating between them.
Where WebP fits
Format selection and size selection are separate axes and compose cleanly. Each <source> in a <picture> element carries its own complete srcset, so you offer WebP at every width and JPG at every width, and the browser resolves both dimensions at once.
Convert to WebP at quality 80-85 for each width — and generate each variant from the full-resolution master, not by downscaling an already-compressed WebP. Compressing twice compounds the loss for no benefit.
The high-DPI trap
A "2x" phone screen does not need an image twice as large in every dimension to look sharp. Photographic content at 1.5x device pixel ratio is generally indistinguishable from 2x, and the file is roughly half the size. Line art and screenshots are the exception — hard edges genuinely need the full pixel density.
If you serve one blanket 2x variant to every retina device, you are paying full price for a difference most visitors cannot see on photographs.
How to verify it works
Open DevTools, switch to the Network tab, filter to images, and set device emulation to a phone. Look at which file was actually requested and what it weighs. Then do the same at desktop width. If the same file is served in both cases, your sizes attribute is wrong — and that single check catches the majority of broken responsive image setups.