Fully understand how srcset and sizes work to serve the optimal image.

Fully understand how srcset and sizes work to serve the optimal image.

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
  1. Evaluate the media condition (min-width:768px).
  2. If it’s true, use 45vw; if false, use 36vw as the source-size value—an assumed rendering width.
  3. 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

  1. Evaluate sizes

    • Example: window.innerWidth = 412 px(min-width:768px) is false → use 36 vw.
    • Calculation: 412 × 0.36 ≈ 148.3 px.
  2. Multiply by DPR

    • Example: devicePixelRatio = 2.625.
    • Required pixels: 148.3 × 2.625 ≈ 389.3 px.
  3. Pick the first srcset candidate ≥ required pixels

    • 400w 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 the srcset 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!