Responsive Images: Stop Serving Desktop Photos to Phone Screens
A single <img> tag serving one resolution to every device is one of the most wasteful patterns on the web. srcset and sizes fix this in one step.
A photography portfolio can look great on a desktop, where the connection is fast and the screen is wide, and still take over ten seconds to load on a phone. The usual cause is a plain <img> tag serving one full-resolution image to every device. The phone downloads a 1800×1200px image and renders it at 390×260px — wasting 95% of the bytes transferred.
The Problem with a Single src
When you write <img src="photo.jpg">, every browser on every device downloads the same file. A 2 MB image works fine on a desktop with a fast connection and a 1920px display. On a 390px phone screen over 4G, that same 2 MB image loads in 3–5 seconds before anything is displayed — and only 5% of those pixels are visible.
srcset and sizes: The Native Solution
The srcset attribute tells the browser which image files are available at which widths. The sizes attribute tells the browser how wide the image will render at different viewport widths. The browser combines this information with the current viewport width and device pixel ratio to download exactly the right file.
<!-- Provide three source widths; browser picks the right one -->
<img
src="photo-800w.jpg"
srcset="
photo-400w.jpg 400w,
photo-800w.jpg 800w,
photo-1600w.jpg 1600w
"
sizes="
(max-width: 600px) 100vw,
(max-width: 1200px) 50vw,
800px
"
alt="Product photo"
width="800"
height="533"
loading="lazy"
>
<!--
sizes explanation:
- On screens under 600px: image is 100% of viewport width
- On screens 600–1200px: image is 50% of viewport width
- On larger screens: image is 800px wide (fixed)
The browser multiplies by devicePixelRatio to select the source.
-->Generating the Size Variants
# Generate multiple sizes with ImageMagick
convert photo.jpg -resize 400x photo-400w.jpg
convert photo.jpg -resize 800x photo-800w.jpg
convert photo.jpg -resize 1600x photo-1600w.jpg
# Then compress each to target quality:
# mogrify -quality 80 photo-400w.jpg photo-800w.jpg photo-1600w.jpg
# Or use a build-time tool like sharp (Node.js):
# sharp('photo.jpg').resize(400).toFile('photo-400w.webp')| Variant | Width | Typical file size (WebP q75) | Used for |
|---|---|---|---|
| xs | 400px | 20–60 KB | Mobile portrait, thumbnails |
| sm | 800px | 60–150 KB | Tablet, half-width desktop |
| md | 1200px | 120–300 KB | Full-width desktop (standard) |
| lg | 1600px | 200–500 KB | Retina / 2× DPR desktop |
| xl | 2400px | 400–900 KB | Hero image for large monitors |
For most websites, three sizes are enough: ~400px (mobile), ~800px (tablet), ~1600px (desktop). More variants improve precision marginally but multiply storage and build time.
WebP with JPEG Fallback via picture
<picture>
<source
type="image/webp"
srcset="photo-400w.webp 400w, photo-800w.webp 800w, photo-1600w.webp 1600w"
sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 800px"
>
<img
src="photo-800w.jpg"
srcset="photo-400w.jpg 400w, photo-800w.jpg 800w, photo-1600w.jpg 1600w"
sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 800px"
alt="Product photo"
width="800" height="533"
loading="lazy"
>
</picture>Framework Shortcuts
- ●Next.js:
<Image>component handles srcset, sizes, WebP conversion, and lazy loading automatically - ●Nuxt.js:
nuxt/imagemodule provides the same - ●Astro: Built-in
<Image>and<Picture>components with automatic optimisation - ●Cloudinary / Imgix: CDN-level responsive images via URL parameters — no build step required
Before adding srcset, make sure each size variant is properly compressed. Use our image resizer to generate the right dimensions, then compress each variant before deploying.
Ready to try it?
All tools run entirely in your browser — no uploads, no account required.
Resize Image