Imágenes responsivas: srcset, sizes y formatos modernos
Domina la entrega de imágenes responsivas con srcset, sizes, el elemento picture y formatos como AVIF y WebP: páginas más rápidas, igual calidad.

Las imágenes representan la mayoría de los bytes transferidos en la mayoría de las páginas web. Servir una imagen principal de 2400px a una pantalla móvil de 375px desperdicia ancho de banda, hunde las puntuaciones de rendimiento y frustra a los usuarios con conexiones más lentas. Aun así, la mayoría de los desarrolladores sigue enviando un único tamaño de imagen y dando el trabajo por terminado.
Las imágenes responsivas resuelven esto al permitir que el navegador elija la imagen óptima según el ancho del viewport, la densidad de píxeles y el soporte de formatos. El HTML ya cuenta con las herramientas—srcset, sizes y el elemento <picture>. El reto está en entender cómo funcionan juntas.
El problema de las imágenes de una sola fuente
Una sola etiqueta <img> obliga a todos los dispositivos a descargar el mismo archivo, sin importar el tamaño de pantalla o las capacidades del dispositivo.
<!-- ❌ One size for all devices -->
<img
src="/images/hero-2400.jpg"
alt="Mountain landscape at sunrise"
width="2400"
height="1600"
/>
<!--
Mobile user on 3G: downloads 800KB for a 375px screen
Desktop user on fiber: gets the right size by accident
Neither gets modern formats like AVIF or WebP
--><!-- ✅ Responsive with srcset and sizes -->
<img
src="/images/hero-800.jpg"
srcset="
/images/hero-400.jpg 400w,
/images/hero-800.jpg 800w,
/images/hero-1200.jpg 1200w,
/images/hero-1600.jpg 1600w,
/images/hero-2400.jpg 2400w
"
sizes="
(max-width: 640px) 100vw,
(max-width: 1024px) 80vw,
1200px
"
alt="Mountain landscape at sunrise"
width="2400"
height="1600"
loading="lazy"
decoding="async"
/>El atributo srcset le indica al navegador qué anchos de imagen están disponibles. El atributo sizes le indica al navegador con qué ancho se renderizará la imagen en cada punto de ruptura. El navegador combina esta información con la densidad de píxeles del dispositivo para elegir el archivo óptimo.
Cómo funcionan srcset y sizes
El descriptor w en srcset comunica el ancho intrínseco de cada archivo de imagen. El navegador usa sizes para determinar el ancho renderizado y luego elige la mejor coincidencia.
<!--
How the browser decides:
Device: iPhone 14 (390px viewport, 3x DPR)
sizes says: at max-width 640px → 100vw → 390px
Effective pixels needed: 390 × 3 = 1170px
Browser picks: hero-1200.jpg (closest match ≥ 1170)
Device: MacBook Pro (1440px viewport, 2x DPR)
sizes says: at max-width 1024px? No.
Falls through to default: 1200px
Effective pixels needed: 1200 × 2 = 2400px
Browser picks: hero-2400.jpg
-->
<img
src="/images/hero-800.jpg"
srcset="
/images/hero-400.jpg 400w,
/images/hero-800.jpg 800w,
/images/hero-1200.jpg 1200w,
/images/hero-1600.jpg 1600w,
/images/hero-2400.jpg 2400w
"
sizes="
(max-width: 640px) 100vw,
(max-width: 1024px) 80vw,
1200px
"
alt="Dashboard analytics view"
width="2400"
height="1600"
/>Error común: fijar sizes en 100vw siempre. En escritorio, las imágenes dentro de un contenedor rara vez ocupan todo el ancho del viewport. Unos valores de sizes inexactos hacen que el navegador descargue imágenes demasiado grandes.
<!-- ❌ sizes always 100vw wastes bandwidth on desktop -->
<img
srcset="
/images/card-400.jpg 400w,
/images/card-800.jpg 800w
"
sizes="100vw"
alt="Product card"
/>
<!-- ✅ Accurate sizes match actual layout -->
<img
srcset="
/images/card-400.jpg 400w,
/images/card-800.jpg 800w
"
sizes="
(max-width: 640px) 100vw,
(max-width: 1024px) 50vw,
33vw
"
alt="Product card"
/>El elemento picture para la negociación de formatos
Mientras que srcset gestiona el cambio de resolución, <picture> gestiona la negociación de formatos y la dirección de arte. El navegador elige el primer <source> que sea compatible.
<picture>
<!-- AVIF: smallest file, newest format -->
<source
type="image/avif"
srcset="
/images/hero-400.avif 400w,
/images/hero-800.avif 800w,
/images/hero-1200.avif 1200w,
/images/hero-1600.avif 1600w
"
sizes="(max-width: 640px) 100vw, 1200px"
/>
<!-- WebP: good compression, wide support -->
<source
type="image/webp"
srcset="
/images/hero-400.webp 400w,
/images/hero-800.webp 800w,
/images/hero-1200.webp 1200w,
/images/hero-1600.webp 1600w
"
sizes="(max-width: 640px) 100vw, 1200px"
/>
<!-- JPEG fallback: universal support -->
<img
src="/images/hero-800.jpg"
srcset="
/images/hero-400.jpg 400w,
/images/hero-800.jpg 800w,
/images/hero-1200.jpg 1200w,
/images/hero-1600.jpg 1600w
"
sizes="(max-width: 640px) 100vw, 1200px"
alt="Mountain landscape at sunrise"
width="1600"
height="1067"
loading="lazy"
decoding="async"
/>
</picture>AVIF normalmente logra archivos un 50% más pequeños que JPEG con una calidad visual equivalente. WebP se ubica entre ambos. Al listar AVIF primero, los navegadores compatibles obtienen los archivos más pequeños, mientras que los navegadores más antiguos recurren a WebP o JPEG.
Dirección de arte con media queries
A veces distintos viewports necesitan recortes de imagen fundamentalmente diferentes, no solo distintas resoluciones. Una imagen principal panorámica en escritorio podría necesitar un recorte cuadrado en móvil para mantener visible al sujeto.
<picture>
<!-- Mobile: square crop focused on subject -->
<source
media="(max-width: 640px)"
srcset="
/images/hero-mobile-400.avif 400w,
/images/hero-mobile-800.avif 800w
"
sizes="100vw"
type="image/avif"
/>
<source
media="(max-width: 640px)"
srcset="
/images/hero-mobile-400.webp 400w,
/images/hero-mobile-800.webp 800w
"
sizes="100vw"
type="image/webp"
/>
<!-- Desktop: wide landscape -->
<source
srcset="
/images/hero-desktop-1200.avif 1200w,
/images/hero-desktop-1600.avif 1600w,
/images/hero-desktop-2400.avif 2400w
"
sizes="100vw"
type="image/avif"
/>
<img
src="/images/hero-desktop-1200.jpg"
srcset="
/images/hero-desktop-1200.jpg 1200w,
/images/hero-desktop-1600.jpg 1600w,
/images/hero-desktop-2400.jpg 2400w
"
sizes="100vw"
alt="Team collaborating in an open office"
width="2400"
height="800"
/>
</picture>Automatización de la generación de imágenes con Sharp
Crear manualmente múltiples tamaños y formatos no es sostenible. Automatiza el proceso con Sharp en un script de compilación.
import sharp from "sharp";
import { readdir, mkdir } from "node:fs/promises";
import { join, parse } from "node:path";
const INPUT_DIR = "images/originals";
const OUTPUT_DIR = "public/images";
const WIDTHS = [400, 800, 1200, 1600, 2400];
const FORMATS = ["avif", "webp", "jpg"] as const;
type ImageFormat = (typeof FORMATS)[number];
const FORMAT_OPTIONS: Record<ImageFormat, object> = {
avif: { quality: 60, effort: 6 },
webp: { quality: 75, effort: 5 },
jpg: { quality: 80, mozjpeg: true },
};
async function processImage(
inputPath: string
): Promise<void> {
const { name } = parse(inputPath);
const image = sharp(inputPath);
const metadata = await image.metadata();
const originalWidth = metadata.width ?? 2400;
for (const format of FORMATS) {
for (const width of WIDTHS) {
if (width > originalWidth) continue;
const outputPath = join(
OUTPUT_DIR,
`${name}-${width}.${format}`
);
await sharp(inputPath)
.resize(width, null, {
withoutEnlargement: true,
fit: "inside",
})
.toFormat(format, FORMAT_OPTIONS[format])
.toFile(outputPath);
console.log(` ✅ ${name}-${width}.${format}`);
}
}
}
async function main() {
await mkdir(OUTPUT_DIR, { recursive: true });
const files = await readdir(INPUT_DIR);
const images = files.filter((f) =>
/\.(jpg|jpeg|png|tiff)$/i.test(f)
);
console.log(
`Processing ${images.length} images...\n`
);
for (const file of images) {
console.log(`📸 ${file}`);
await processImage(join(INPUT_DIR, file));
}
console.log("\n✅ All images processed");
}
main();Implementación en React/Next.js
Los frameworks modernos suelen abstraer las imágenes responsivas, pero entender el HTML subyacente ayuda cuando esa abstracción se queda corta.
// ResponsiveImage.tsx
interface ImageProps {
src: string; // base name without extension
alt: string;
width: number;
height: number;
sizes: string;
priority?: boolean;
className?: string;
}
const WIDTHS = [400, 800, 1200, 1600, 2400];
function buildSrcSet(
base: string,
format: string
): string {
return WIDTHS.map(
(w) => `/images/${base}-${w}.${format} ${w}w`
).join(", ");
}
export function ResponsiveImage({
src,
alt,
width,
height,
sizes,
priority = false,
className,
}: ImageProps) {
return (
<picture>
<source
type="image/avif"
srcSet={buildSrcSet(src, "avif")}
sizes={sizes}
/>
<source
type="image/webp"
srcSet={buildSrcSet(src, "webp")}
sizes={sizes}
/>
<img
src={`/images/${src}-800.jpg`}
srcSet={buildSrcSet(src, "jpg")}
sizes={sizes}
alt={alt}
width={width}
height={height}
loading={priority ? "eager" : "lazy"}
decoding={priority ? "sync" : "async"}
fetchPriority={priority ? "high" : "auto"}
className={className}
/>
</picture>
);
}
// Usage
<ResponsiveImage
src="hero-landscape"
alt="Mountain landscape at sunrise"
width={2400}
height={1600}
sizes="(max-width: 640px) 100vw, 1200px"
priority
/>Puntos clave
Usa srcset con descriptores de ancho (w) para ofrecer múltiples resoluciones de imagen y sizes para indicarle al navegador el ancho renderizado en cada punto de ruptura—unos valores de sizes precisos son fundamentales porque el navegador los usa para calcular qué imagen descargar antes de que ocurra el layout. El elemento <picture> gestiona la negociación de formatos listando las fuentes en orden de preferencia (AVIF primero, luego WebP, y JPEG como respaldo), permitiendo que cada navegador descargue el formato más eficiente que soporte. La dirección de arte requiere elementos <source> separados con atributos media cuando distintos viewports necesitan recortes diferentes—el cambio de resolución con srcset solo escala la misma imagen, no cambia la composición. Automatiza la generación de imágenes con herramientas como Sharp para crear todas las variantes de tamaño y formato a partir de los archivos fuente durante la compilación, eliminando el procesamiento manual de imágenes y garantizando un resultado consistente en cada imagen del proyecto.


