Serving the right image size to each device is one of the highest-impact performance optimizations you can make. A 3000px hero image on a 375px phone screen wastes bandwidth and slows LCP. The srcset and sizes attributes solve this with native HTML — no JavaScript required.
The problem: one image for all devices
Without responsive images, you typically serve the largest image and let CSS scale it down. This works visually but wastes bandwidth: a 400KB desktop image costs as much on mobile as on desktop, even though a 60KB version would look identical on a small screen.
srcset: defining image variants
The srcset attribute lists available image files and their widths:
<img
src="hero-800.webp"
srcset="hero-400.webp 400w,
hero-800.webp 800w,
hero-1600.webp 1600w"
alt="Hero image"
/>
The browser reads the descriptor (400w = 400 pixels wide) and the current viewport width to select the best file. Without a sizes attribute, it assumes the image will be 100vw wide — usually wrong.
sizes: telling the browser the display width
The sizes attribute describes how wide the image will appear at different viewport breakpoints:
<img
srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1600.webp 1600w"
sizes="(max-width: 640px) 100vw,
(max-width: 1024px) 50vw,
800px"
alt="Hero image"
/>
This tells the browser: on mobile (<640px) the image is full-width; on tablet it's 50% of the viewport; on desktop it's always 800px. The browser combines this with the device pixel ratio to pick the optimal file.
Device pixel ratio
Retina displays have a device pixel ratio (DPR) of 2 or 3, meaning they need 2–3× more pixels to render crisply. A browser on a 375px Retina display with sizes="100vw" will actually request the 800w image (375 × 2 DPR ≈ 750px → nearest variant is 800w). This is handled automatically when you provide enough variants.
The picture element: art direction
For cases where you want to serve a completely different crop on mobile (not just smaller), use <picture>:
<picture>
<source media="(max-width: 640px)" srcset="hero-square-400.webp" />
<source media="(min-width: 641px)" srcset="hero-wide-1600.webp" />
<img src="hero-wide-1600.webp" alt="Hero image" />
</picture>
Modern format delivery with picture
Use <picture> to serve AVIF to browsers that support it, with WebP and JPEG as fallbacks:
<picture>
<source type="image/avif" srcset="hero.avif" />
<source type="image/webp" srcset="hero.webp" />
<img src="hero.jpg" alt="Hero image" />
</picture>
Combine with srcset on each source for the full responsive + format solution.
Next.js: automatic srcset generation
Next.js's <Image> component generates srcset automatically based on the deviceSizes and imageSizes config. The sizes prop maps directly to the HTML sizes attribute:
<Image
src="/hero.jpg"
width={1600}
height={900}
sizes="(max-width: 640px) 100vw, 800px"
alt="Hero"
/>
Without the sizes prop, Next.js defaults to 100vw — set it correctly to avoid loading unnecessarily large images. See our next/image guide for the full trade-off analysis.
Generating variants at scale
For static sites, pre-generate variants at build time using Sharp:
sharp('hero.jpg').resize(400).webp().toFile('hero-400.webp')
For user-uploaded content, use an image CDN that generates variants on the fly. See our image CDN comparison.
For manual one-off conversions, use Picovert's PNG to WebP converter.
Quick checklist
- Always set
sizes— don't rely on the100vwdefault - Provide at least 3 width variants per image (small, medium, large)
- Include 2× variants for Retina support
- Lazy-load below-fold images with
loading="lazy" - Preload the LCP image with
<link rel='preload'>