← All writing
Craft · · 7 min

srcset, revisited

On the four things that actually changed, and the fact that the browser is finally allowed to just look.

Images Responsive Performance

I audited a site in April that was built last year. Responsive images everywhere, generated by the build, four widths per image, AVIF and JPEG, all of it correct.

On a phone, every image on the page downloaded at 1600 pixels wide.

Which is the same bug, exactly, that I wrote a post about in January 2018. Same cause, same symptom, eight years and an entire generation of tooling later. So this seems like a reasonable moment to go back over it.

The bug, still

sizes defaults to 100vw. If you don’t write one, the browser assumes every image will be displayed at the full width of the viewport, picks accordingly, and on a narrow screen with a 2× display that reasoning lands on your largest file.

It looks completely fine. The images are sharp, nothing is broken, and unless you open the network panel and look at what was actually fetched, there is no symptom at all. That’s why it survives code review and why it was still there a year after launch.

Everything else about this has improved. That hasn’t moved an inch.

Four things that did change

width and height came back from the dead. In 2018 I’d have told you to strip them, because CSS does the sizing and the attributes are a relic. Then browsers started using them to compute an aspect ratio and reserve the space before the file arrives, and suddenly the two oldest attributes in HTML were the single biggest fix for layout shift. Put them on everything, always, with the intrinsic dimensions of the file. This is the highest-value thing in the post and it has nothing to do with srcset.

loading="lazy" made the whole question smaller. An image that never gets fetched doesn’t need the right width. It’s one attribute and it’s in everything now, and it did more for the average page than any amount of careful sizes tuning. Not for anything above the fold, mind, and I wrote a whole post in 2020 about the three places where lazy-loading makes things worse, all of which are still true.

fetchpriority="high" on the one image that matters. The hero, the thing that is almost certainly your largest contentful paint. The browser deprioritizes images relative to stylesheets and scripts by default, which is usually right and is wrong for that specific image. One attribute, and on a couple of sites it’s been worth more than everything else I did that day.

And the generation problem got solved by tooling rather than by the platform. The complaint at the end of the 2018 post was maintenance: four files per image, by hand, regenerated whenever anything changed, and nobody would keep it up. That was correct and it has been comprehensively fixed. Every static site generator and every framework now has an image component or plugin that takes one source file and emits the whole set at build time, and image CDNs do the same thing on the fly. Nobody hand-writes a srcset any more, which is precisely why nobody notices when sizes is missing from the template that writes them.

2018 vs now
<!-- what I was writing in 2018 -->
<img src="hero-800.jpg"
     srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1600.jpg 1600w"
     sizes="(min-width: 60rem) 40rem, 100vw"
     alt="…">

<!-- the hero, now -->
<img src="hero-800.jpg"
     srcset="hero-400.avif 400w, hero-800.avif 800w, hero-1600.avif 1600w"
     sizes="(min-width: 60rem) 40rem, 100vw"
     width="1600" height="900"
     alt="…"
     loading="eager" decoding="async" fetchpriority="high">
HTML

What sizes actually is, still

The framing from 2018 that I’d keep.

srcset is you listing what you’ve got. sizes is you telling the browser how wide the thing will end up on the page, so it can choose before it knows, because it has to choose before layout has happened and it would rather guess than wait.

And the reason it’s a bad attribute is that it’s a copy of your CSS, written in a different language, in the markup, kept in sync by hand. Change a container from 40rem to 44rem and every sizes on the site is now subtly wrong and nothing tells you. It’s the only piece of layout information a modern site is expected to duplicate outside the stylesheet, and it has been that way since the day it shipped.

Which is finally being fixed

sizes="auto" lets the browser skip the guessing and use the width the image actually got.

<img src="thumb-400.avif"
     srcset="thumb-400.avif 400w, thumb-800.avif 800w, thumb-1200.avif 1200w"
     sizes="auto"
     width="1200" height="800"
     alt="…"
     loading="lazy" decoding="async">
HTML

No media queries, no rem values, nothing to keep in sync. The layout is the source of truth, which it should have been all along.

It works because the image is lazy: by the time the browser gets round to fetching it, layout has happened and it can measure the box instead of predicting it. Which is also the catch, and it’s a good one.

Check support before you lean on it rather than trusting a post you’re reading a year after it was written. It’s been in two engines for a while and the third caught up more recently, and the fallback is harmless anyway: a browser that doesn’t understand auto treats the value as invalid and falls back to 100vw, which is where you were already.

What didn’t change at all

Two things, and they’re the two I’d have most liked to report progress on.

The CMS still undoes it. I wrote that post in 2016 and every word of it stands. The pipeline resizes what the developer commits. The client uploads a 4,000-pixel photograph straight off a camera through an admin form, and whether that gets handled depends entirely on whether somebody wired the transformation into the upload path, which is a decision about the CMS and not about HTML.

And none of this is the first thing to check. If an image is the wrong dimensions for its slot, or was exported at quality 100 by somebody who has never opened it since, then no amount of srcset is going to save it. The attributes decide which of your files gets sent. They have nothing whatsoever to say about whether the files were any good.

The usual shape

Eight years, and the honest summary is that the attribute I wrote 1,500 words about in 2018 has been quietly demoted. width, height, loading and fetchpriority between them are worth more on a typical page than every hour I’ve spent tuning breakpoint lists, and three of those four are things you set once and never think about again.

Which is the usual shape, isn’t it. The thing that turned out to matter was boring, was already in the language, and I’d spent the previous few years being told to delete it.

Read similar posts
9 min

Just look at it

Most image optimization advice is about codecs, but the actual bug is usually that nobody has opened the file and looked at it since the day it came out of Figma.

7 min

AVIF and the format question

Two things happened to image formats this fall, and the less exciting one is much more useful. I'd like a flowchart for choosing between them. I don't think one can exist.