You've probably noticed your phone crawling to load a 2MB desktop image, while your site's Lighthouse score tanks. Responsive images with srcset let the browser download the right file size for each device—cutting bandwidth, improving Core Web Vitals, and making your users actually want to visit your site. In this post, I'll walk you through how srcset works, why it matters for SEO, and how to stop wasting bytes on oversized images.
The problem with fixed image sizes
When I first built production platforms at my previous gig, we'd drop a single high-res image into the HTML and call it done. A 2000×1500px image optimized for desktop monitors gets shipped to every device—phones, tablets, smartwatches, everything. On a 3G connection, that 800KB download tanks your First Contentful Paint. On a 4K display, it looks fuzzy. You're either wasting bandwidth or degrading UX, and usually both.
Browsers can't mind-read. Without guidance, they assume you want the same image everywhere. That's where srcset comes in: it tells the browser "here are several versions of this image at different sizes, pick the one that fits your viewport."
srcset lists image widths
The srcset attribute is a comma-separated list of image URLs paired with descriptors. Those descriptors tell the browser how wide each file is. Let's say you're building an e-commerce product card:
<img src="card-800.webp"
srcset="card-400.webp 400w, card-800.webp 800w, card-1200.webp 1200w"
alt="Blue running shoe">The 400w, 800w, and 1200w mean "this image is 400 pixels wide," "this one is 800 pixels wide," and so on. The w unit is image width in CSS pixels. You're not specifying device width yet—just the actual pixel dimensions of each file you've prepared.
In practice, I prepare three to four versions of each image:
- A small version for phones (400px or 480px)
- A medium version for tablets (800px)
- A large version for desktops (1200px or 1600px)
The browser's pixel density also matters. On a 2x retina display, a phone might render 400 CSS pixels using a 800px physical image. That's why you often need both a 400w and an 800w—they serve different purposes on different hardware.
sizes tells the browser the display width
Here's the tricky part: the browser doesn't know how wide your image will actually display on the user's screen. Your card component might take 100% of the viewport on mobile but only 300px on desktop. That's where the sizes attribute comes in.
sizes is a comma-separated list of media queries paired with display widths. Let's extend our earlier example:
<img src="card-800.webp"
srcset="card-400.webp 400w, card-800.webp 800w, card-1200.webp 1200w"
sizes="(max-width: 600px) 100vw, (max-width: 1024px) 50vw, 400px"
alt="Blue running shoe">Read it as: "On screens up to 600px wide, this image is 100% of the viewport. On screens up to 1024px, it's 50% of the viewport. Everywhere else, it's 400px." The browser now knows:
- The image dimensions available in srcset (400w, 800w, 1200w)
- How wide the image will display (from sizes)
- The device's pixel density (2x on iPhones, 1x on most Android)
With that information, it picks the smallest image that still looks sharp. On a iPhone 14 Pro (390px viewport, 2x density), the browser calculates: 390px × 2 = 780px needed, so grab the 800w image. On a desktop (1440px, 1x density), it calculates: 400px × 1 = 400px needed, so grab the 400w image. You've just saved that desktop user 800KB.
The browser picks the best file
This is the beautiful part: you define the options, the browser makes the choice based on real hardware and network conditions. You don't hardcode which image goes where.
Here's a realistic scenario from a travel booking platform I worked on. We had a destination card showing a hero image:
<picture>
<source
media="(min-width: 1200px)"
srcset="destination-1200.webp 1200w, destination-1600.webp 1600w">
<source
media="(min-width: 768px)"
srcset="destination-600.webp 600w, destination-900.webp 900w">
<source
media="(max-width: 767px)"
srcset="destination-400.webp 400w, destination-800.webp 800w">
<img src="destination-600.webp" alt="Bali beach sunset">
</picture>On different devices, different images load:
- iPhone SE (375px, 2x): Browser picks the 800w file (375 × 2 = 750px needed)
- iPad (768px, 2x): Browser picks the 900w file (768 × 2 = 1536px needed, closest match)
- Desktop (1440px, 1x): Browser picks the 1600w file (1440 × 1 = 1440px needed)
The fallback <img> tag loads if the browser doesn't support srcset (ancient IE, basically nobody now). Modern browsers all support it—Chrome, Safari, Firefox, Edge.
Frameworks generate this for you
Writing srcset and sizes by hand for every image on your site is tedious and error-prone. Good news: modern frameworks handle it automatically.
Next.js Image component:
<Image
src="/destination.webp"
alt="Bali beach"
width={1200}
height={800}
responsive={true}
/>Next.js generates srcset automatically, picks multiple widths based on your config, serves WebP to modern browsers, and optimizes on-build or on-demand. You get responsive images with basically zero effort.
Astro:
<Image src={import('./destination.webp')}
alt="Bali beach"
widths={[400, 800, 1200]}
/>Nuxt/Nuxt Image:
<NuxtImg src="/destination.webp"
alt="Bali beach"
sizes="sm:100vw md:50vw lg:400px"
/>All three generate optimized images, serve modern formats like WebP and AVIF, and generate proper srcset automatically. If you're using plain HTML, you have to do it manually—but even then, you can use build tools like Sharp (Node.js) or ImageMagick to generate variants at build time.
Common mistakes
Forgetting the fallback src: Always include a regular src attribute. Older browsers and email clients ignore srcset. Pick a medium-sized image that doesn't look terrible anywhere.
sizes that don't match reality: If you tell the browser "this image is 100vw" but it actually renders at 50vw, the browser overshoots and wastes bandwidth. Inspect your layout in DevTools, measure the actual display width, and update sizes to match.
Inconsistent breakpoints: If your card is 100vw on mobile and 400px on desktop, but your sizes says (max-width: 768px) 50vw, you're lying. The browser will pick a smaller image than needed, and it'll look blurry on high-DPI screens.
Too many srcset variants: You don't need 20 image sizes. Three to four is usually enough: one for mobile, one for tablet, one for desktop, maybe one for 4K. Diminishing returns kick in fast.
Forgetting density descriptors: For retina displays, you might need both a 400w and an 800w version of the same logical image size. A 400w file on a 2x display shows up blurry. Use both.
Not measuring actual impact: After implementing responsive images, check your Network tab in DevTools. Open the page on real devices. Are the right images actually loading? Use Google's PageSpeed Insights to confirm image optimization scores.
Key takeaways
- Responsive images with srcset let you serve different file sizes to different devices, cutting bandwidth and improving Core Web Vitals.
srcsetlists image variants with widths (400w, 800w, 1200w);sizestells the browser how wide the image displays at different breakpoints.- The browser calculates: display width × pixel density = pixels needed, then picks the smallest image that still looks sharp.
- Modern frameworks (Next.js, Astro, Nuxt) generate srcset and image variants automatically—use them.
- Always include a fallback
srcand ensure yoursizesmath matches your actual CSS layout. - Measure before and after: check the Network tab to confirm the right images load on real devices.






Comments
Loading comments…