Images are typically 50%+ of page weight. Serving the right image for each device is a massive performance win.
The Problem
A 2000px hero image on a 375px phone wastes 80%+ of bandwidth. A 375px image on a 4K display looks blurry.
srcset: Resolution Switching
<img
src="hero-800.jpg"
srcset="
hero-400.jpg 400w,
hero-800.jpg 800w,
hero-1200.jpg 1200w,
hero-1600.jpg 1600w
"
sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 800px"
alt="Hero image"
width="1600"
height="900"
loading="lazy"
/>How sizes Works
Tells the browser how wide the image will display. The browser then picks the best srcset candidate:
- On a 375px phone (2x DPR): needs 750px → picks hero-800.jpg
- On a 1440px desktop: image is 50vw = 720px → picks hero-800.jpg
- On a 2560px 4K: image is 800px display, 2x DPR = 1600px → picks hero-1600.jpg
Art Direction with picture
<picture>
<source media="(max-width: 600px)" srcset="hero-mobile.webp" type="image/webp">
<source media="(max-width: 600px)" srcset="hero-mobile.jpg">
<source srcset="hero-desktop.webp" type="image/webp">
<img src="hero-desktop.jpg" alt="Hero" width="1600" height="900">
</picture>Modern Formats
| Format | vs JPEG | Support | Best For |
|---|---|---|---|
| WebP | 25-35% smaller | 97%+ browsers | General use |
| AVIF | 50% smaller | 92%+ browsers | Photos, complex images |
| JPEG XL | 60% smaller | Limited (Safari) | Future standard |
Lazy Loading
<!-- Native lazy loading -->
<img src="photo.jpg" loading="lazy" alt="Photo">
<!-- Don't lazy load above-the-fold images! -->
<img src="hero.jpg" fetchpriority="high" alt="Hero">CLS Prevention
<!-- Always set width and height -->
<img src="photo.jpg" width="800" height="600" alt="Photo">
/* Or use aspect-ratio */
img {
aspect-ratio: 16 / 9;
width: 100%;
height: auto;
}Quick Checklist
- Use WebP/AVIF with JPEG fallback
- Serve multiple sizes via srcset
- Set width/height to prevent CLS
- Lazy load below-the-fold images
- Use fetchpriority="high" for LCP image
- Compress: 80% quality is usually indistinguishable