Intended Audience
- Anyone who wants images to load faster
- Developers frustrated that
srcset
is not picking the intended resolution - People who need to switch image sizes with media queries
What You’ll Gain from This Article
- A systematic understanding of how
srcset
chooses image candidates - How to write the
sizes
attribute and decide its optimal values for each viewport & DPR
Introduction
While working to improve the performance metric LCP (Largest Contentful Paint), I struggled to make the browser pick the one target image from the multiple resolutions I prepared in srcset
. It turned out I hadn’t fully grasped the browser’s image-selection logic.
This article breaks that algorithm down—together with the debugging workflow I used—so you can master srcset / sizes
once and for all.
React Component Example
Below is a real component that renders a blog hero image. Inside <picture>
the sources are listed in order AVIF → WebP → <img>
fallback.
import { useEffect, useRef } from "react";
import style from "./BlogHero.module.css";
export const BlogHero = () => {
const imgRef = useRef<HTMLImageElement>(null);
// Debug: log what the browser actually chose
useEffect(() => {
if (!imgRef.current) return;
const { currentSrc, sizes } = imgRef.current;
console.table({
currentSrc,
innerWidth: window.innerWidth,
sizes,
DPR: window.devicePixelRatio,
slotWidth:
Math.round(imgRef.current.getBoundingClientRect().width) + " px (actual)",
});
}, []);
const sizes = "(min-width:768px) 45vw, 36vw"; // 45 vw ≥ 768 px, otherwise 36 vw
return (
<picture>
<source
type="image/avif"
srcSet="/img/hero/blog/kameka_blog@400.avif 400w,
/img/hero/blog/kameka_blog@800.avif 800w,
/img/hero/blog/kameka_blog@1200.avif 1200w"
sizes={sizes}
/>
<source
type="image/webp"
srcSet="/img/hero/blog/kameka_blog@400.webp 400w,
/img/hero/blog/kameka_blog@800.webp 800w,
/img/hero/blog/kameka_blog@1200.webp 1200w"
sizes={sizes}
/>
<img
ref={imgRef}
width={1200}
height={800}
src="/img/hero/blog/kameka_blog@400.webp" // minimal fallback
alt="Blog hero image"
srcSet="/img/hero/blog/kameka_blog@400.webp 400w,
/img/hero/blog/kameka_blog@800.webp 800w,
/img/hero/blog/kameka_blog@1200.webp 1200w"
sizes={sizes}
className={style.heroImg}
/>
</picture>
);
};
srcset
Syntax and Width Descriptors
/img/...@400.avif 400w
/img/...@800.avif 800w
- The trailing
400w
,800w
, etc. are width descriptors. - They tell the browser “this candidate can satisfy layouts that need at least 400 CSS px of intrinsic width,” and so on.
- Pixel-density descriptors (
2x
,3x
, …) also exist, but this article focuses on width descriptors.
sizes
Syntax and Role
(min-width:768px) 45vw, 36vw
- Evaluate the media condition
(min-width:768px)
. - If it’s
true
, use45vw
; iffalse
, use36vw
as the source-size value—an assumed rendering width. - It is not the actual rendered width; it’s merely an input for choosing a
srcset
candidate.
What the console.table
Columns Mean
Field | Description |
---|---|
currentSrc | The URL the browser finally loaded—so you instantly see which candidate won. |
innerWidth | window.innerWidth (CSS px); used to evaluate the media condition in sizes . |
sizes | The raw sizes string, pre-evaluation—handy for double-checking what you told the browser. |
DPR | window.devicePixelRatio ; multiplied when computing required pixels. |
slotWidth | Actual rendered width from getBoundingClientRect().width ; compare it with the theoretical value (sizes × DPR ). |
Sample Output
{
"currentSrc": "http://localhost:3000/img/hero/blog/kameka_blog@400.avif",
"innerWidth": 412,
"sizes": "(min-width:768px) 45vw, 36vw",
"DPR": 2.625,
"slotWidth": "346 px (actual)"
}
How the Browser Chooses an Image: Step-by-Step Algorithm
-
Evaluate
sizes
- Example:
window.innerWidth = 412 px
→(min-width:768px)
isfalse
→ use 36 vw. - Calculation:
412 × 0.36 ≈ 148.3 px
.
- Example:
-
Multiply by DPR
- Example:
devicePixelRatio = 2.625
. - Required pixels:
148.3 × 2.625 ≈ 389.3 px
.
- Example:
-
Pick the first
srcset
candidate ≥ required pixels400w
is the smallest that meets the need →/kameka_blog@400.avif
is selected.
Summary
sizes
conveys “about how wide I expect to render this image” to the browser.- The browser computes
sizes × DPR
, then walks thesrcset
list from smallest to largest until it finds the first candidate big enough. - When things go wrong, inspect currentSrc / sizes / DPR / slotWidth with DevTools and
console.table
; the culprit is usually obvious.
With this knowledge, you can confidently serve the exact images you intended—and translate that into real-world performance wins such as better LCP scores. Good luck optimizing!