Responsive images exist to solve one problem: a phone should not download the image a 27-inch monitor needs. Two attributes do almost all the work, and one of them is routinely left out.
srcset and sizes
<img
src="/hero-1200.webp"
srcset="/hero-600.webp 600w, /hero-1200.webp 1200w, /hero-2400.webp 2400w"
sizes="(max-width: 768px) 100vw, 1200px"
width="1200" height="630"
alt="..."
/>
srcset lists candidates with their intrinsic widths. The w descriptor is the image's actual pixel width, not the size it displays at.
sizes tells the browser how wide the image will render at each breakpoint. Without it, the browser assumes 100vw and picks the largest candidate on every screen - so adding srcset without sizes can make things worse than having neither.
src is the fallback for anything that does not understand srcset.
width and height are not optional. They let the browser reserve space before the image arrives, which is the single biggest fix for layout shift.
The browser chooses before layout is computed, using sizes as your declaration of intent. That is why it must be maintained when your CSS changes - a stale sizes is a silent regression.
Why the browser sometimes picks bigger
Two reasons that look like bugs and are not:
Device pixel ratio. On a 2× display, an image rendering at 600 CSS pixels needs a 1200px source. The browser multiplies for you.
Already cached. If a larger candidate is already in cache, browsers will use it rather than fetch a smaller one. Sensible, and it makes testing confusing - always test with an empty cache.
picture, for a different job
srcset chooses between sizes of the same image. <picture> chooses between different images or formats.
<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>
Use <picture> for format fallbacks, and for art direction - a wide crop on desktop and a square crop on mobile, which is a genuinely different image rather than a smaller one.
Do not use it where srcset would do. It is more markup for the same result.
How many candidates
Three is usually right: roughly 1×, 2× and one in between. More candidates means more files to generate, store and invalidate, for diminishing returns.
Pick widths from your actual breakpoints rather than round numbers. If your container is 1200px at most, a 2400px candidate covers retina and nothing above it is useful.
Free toolImage ResizerResize PNG, JPG, WebP and GIF images to exact pixel dimensions with aspect ratio locked. Runs entirely in your browser - no upload, no queue, no watermark.Loading priority
The LCP image gets fetchpriority="high" and a preload, and must never be lazy. Everything below the fold gets loading="lazy".
Lazy-loading the hero is a common self-inflicted wound: it delays the LCP by a full round trip because the browser will not start the fetch until layout tells it the image is near the viewport.
If you add srcset and your images get bigger rather than smaller, you left out sizes. That is the failure mode, and it is invisible unless you check transferred bytes at a narrow viewport.
Launch it where the numbers are checked
RankCert ranks products on domain control we verify ourselves. Listing is free and the link stays dofollow whether or not you display the badge.
