WebP puede reducir el archivo, pero no elige por sí solo cuántos píxeles descarga el navegador. Si la conversión solo cambia el formato, una imagen de 1.200 píxeles seguirá con 1.200 píxeles, incluso cuando aparezca con la mitad de ese ancho en la pantalla.
Este blog encontró el problema en la prueba automatizada de rendimiento. El 19 de julio de 2026, la imagen del destacado de la portada pasó de un motivo gráfico de cerca de 8 KB a una imagen de aproximadamente 50 KB. En el entorno de integración continua, el Largest Contentful Paint (LCP), el tiempo hasta la renderización del mayor elemento de contenido elegible en el área visible, llegó a 2.723 ms y superó el techo de 2.500 ms.
La corrección no fue degradar la imagen. El generador pasó a producir versiones de 1.200 y 800 píxeles, mientras el HTML ganó srcset y sizes. En una comparación local separada, antes y después en el mismo perfil móvil simulado, el LCP bajó de 2.793 a 2.122 ms. El formato siguió siendo WebP; fue el navegador el que pasó a recibir una alternativa compatible con el espacio ocupado.
El caso original ocurrió con Astro 7.1.1. En la versión en la que se escribió este texto, la implementación se volvió a revisar con Astro 7.2.2 y Node.js 24.15.0; hoy el proyecto usa Astro 7.3.5, y la versión mínima declarada sigue siendo Node.js 22.12.0. srcset y sizes, sin embargo, forman parte del HTML y no dependen de Astro para funcionar.
WebP cambia el formato, no elige el tamaño
Convertir una imagen a WebP puede reducir el archivo, pero no crea automáticamente versiones para distintas pantallas. Si el HTML ofrece solo este recurso, el navegador no tiene alternativa:
<img
src="/images/blog/exemplo/card.webp"
alt=""
width="1200"
height="630"
/>
En este ejemplo didáctico, el CSS puede renderizar la imagen con 360 píxeles de ancho. La descarga, sin embargo, sigue siendo el archivo de 1.200 píxeles indicado en src.
El formato y la resolución resuelven problemas distintos. WebP comprime cada candidato. La imagen responsiva crea candidatos más pequeños e informa en qué contexto puede ser útil cada uno.
La cuenta que orienta la elección
Cuando usa descriptores de ancho (w), el atributo srcset presenta los archivos disponibles y el ancho natural de cada uno. El atributo sizes describe el ancho que la imagen debe ocupar en el diseño, también llamado espacio de visualización.
<img
src="/images/blog/exemplo/card.webp"
srcset="
/images/blog/exemplo/card-800.webp 800w,
/images/blog/exemplo/card.webp 1200w
"
sizes="(min-width: 768px) 720px, calc(100vw - 2rem)"
alt=""
width="1200"
height="630"
/>
El descriptor 800w no redimensiona el archivo. Declara que aquel recurso tiene 800 píxeles de ancho natural. El valor tiene que corresponder al archivo real.
Con esa información, el navegador calcula la densidad efectiva de cada candidato: el ancho del archivo dividido por el ancho previsto en sizes. La elección también puede considerar la densidad de la pantalla, el zoom y las condiciones de red, y el resultado final depende de la implementación del navegador. Por eso, srcset ofrece opciones; no es una orden rígida de descargar siempre el archivo más pequeño.
En el caso del 19 de julio, el sizes móvil era 100vw. El perfil usado por la compuerta tenía 412 píxeles CSS de ancho y una relación de píxeles del dispositivo de 1,75. Para definir la variante más pequeña, el proyecto tomó el ancho entero de la ventana como referencia conservadora: 412 × 1,75, cerca de 721 píxeles nominales. Una versión de 720 píxeles quedaría ligeramente por debajo de ese objetivo. La de 800 píxeles ofreció margen y siguió más cerca que la de 1.200.
El detalle importa porque la densidad transforma una tarjeta aparentemente pequeña en una necesidad mayor de resolución. Medir solo el ancho CSS ignora ese factor.
Las variantes de 800 píxeles ocupaban un 54,6% menos
El generador de portadas de este blog usa Sharp para producir card.webp, de 1.200 × 630 píxeles, y card-800.webp, de 800 × 420. Esta representación abreviada muestra solo la transformación de la variante responsiva:
const card800 = await sharp(variantSource)
.resize(800, 420, { fit: 'cover', position: 'centre' })
.webp({ quality: 75, effort: 6 })
.toBuffer();
Antes de la inclusión de las imágenes de este post, el 21 de agosto de 2026, los 17 pares publicados entonces sumaban:
| Variante | Dimensión | Total en la colección |
|---|---|---|
card.webp | 1.200 × 630 | 939.640 bytes |
card-800.webp | 800 × 420 | 426.294 bytes |
Las variantes de 800 píxeles ocupaban, sumadas, un 54,6% menos de bytes. La diferencia combina menos píxeles con la configuración de compresión de la variante, que usa calidad 75; no es un experimento aislado de resolución. Eso mide los archivos disponibles en el repositorio, no un ahorro garantizado en cada visita. La ganancia de red solo ocurre cuando el navegador selecciona el candidato más pequeño, y la compresibilidad cambia según la imagen.
El mismo archivo ocupa espacios distintos
Crear dos imágenes fue solo la mitad de la corrección. La portada usa imágenes de la misma colección en tres tipos de región con anchos distintos: destacado, grilla de posts recientes y archivo.
| Región | Valor de sizes |
|---|---|
| Destacado | (min-width: 1200px) 1072px, (min-width: 768px) calc(100vw - 128px), (min-width: 640px) calc(100vw - 96px), calc(100vw - 60px) |
| Grilla reciente | (min-width: 1200px) 520px, (min-width: 960px) calc(50vw - 80px), (min-width: 768px) calc(100vw - 128px), (min-width: 640px) calc(100vw - 96px), calc(100vw - 60px) |
| Archivo | (min-width: 640px) 208px, min(208px, calc(100vw - 60px)) |
El srcset puede ser igual en las tres regiones. El sizes no, porque declara una estimación del espacio que ocupa cada componente. Informar 100vw para una tarjeta que ocupa solo una fracción de la ventana hace que el navegador sobrestime la necesidad y tiende a favorecer un archivo más grande.
En forma equivalente y abreviada, la configuración del destacado quedó así:
<img
src={featuredCardImage}
srcset={cardSrcset(featuredCardImage)}
sizes="(min-width: 1200px) 1072px, (min-width: 768px) calc(100vw - 128px), (min-width: 640px) calc(100vw - 96px), calc(100vw - 60px)"
alt=""
loading="eager"
fetchpriority="high"
decoding="async"
width="1200"
height="630"
/>
El sizes no controla el ancho visual; el CSS sigue siendo responsable del diseño. width y height establecen la proporción intrínseca y permiten reservar espacio antes de la carga, lo que ayuda a evitar el desplazamiento de contenido.
El texto alternativo está vacío en este caso porque la portada es decorativa y el titular aparece junto a ella en texto real. Una imagen que comunica información propia necesita una descripción adecuada, no un alt="" copiado de este ejemplo.
La imagen del LCP no puede esperar
En la portada, solo la imagen destacada recibe loading="eager" y fetchpriority="high". Esos atributos ya existían antes de la corrección de este caso; no fueron ellos los que recuperaron la métrica. Permanecen porque la imagen es la candidata probable a LCP y necesita descubrirse pronto. Las portadas debajo de ella usan loading="lazy", porque pueden esperar hasta acercarse al área visible.
Aplicar la carga diferida a la imagen de LCP crea un retraso innecesario. Hacer lo contrario y marcar todas las portadas con prioridad alta también perjudica la jerarquía: si todo es urgente, la indicación deja de ayudar al navegador a decidir.
Esa separación completa la solución. srcset y sizes se ocupan del recurso adecuado; loading y fetchpriority se ocupan del momento y de la prioridad. El presupuesto de Core Web Vitals de este blog sigue siendo la compuerta que confirma si la combinación funcionó.
Cuándo dejar que Astro genere las variantes
Astro puede automatizar buena parte de ese trabajo cuando la imagen está en src/ y se importa mediante astro:assets. La propiedad layout genera srcset y sizes:
---
import { Image } from 'astro:assets';
import capa from '../assets/capa.webp';
---
<Image
src={capa}
alt="Descripción del contenido visual"
layout="constrained"
width={1200}
height={630}
/>
El redimensionamiento visual todavía necesita los estilos responsivos de Astro, habilitados por image.responsiveStyles: true, o de CSS equivalente en el proyecto.
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
image: { responsiveStyles: true },
});
Las portadas de este proyecto ya llegan procesadas a public/images/blog/, por eso el flujo manual es necesario. Astro copia archivos de public/ sin transformación y no crea versiones responsivas para ellos.
La elección, por lo tanto, depende del origen de la imagen. Un archivo importado de src/ puede usar el servicio de Astro. Si permanece en este flujo en public/, el archivo producido por un generador externo tiene que llegar con las variantes listas y recibir el HTML correspondiente.
Cómo verificar sin confiar en el ojo
Una página puede parecer correcta mientras transfiere el archivo equivocado. La verificación tiene que observar la decisión del navegador.
- Ejecute
npm run buildy despuésnpm run previewpara abrir la página en un servidor de previsualización. - Active el perfil móvil antes de recargar, deshabilite la caché en la pestaña de red y haga una carga nueva.
- Inspeccione la propiedad
currentSrc, que muestra qué candidato se seleccionó.
const imagem = document.querySelector('.home-featured img');
({
arquivo: imagem?.currentSrc,
larguraExibida: imagem?.getBoundingClientRect().width,
larguraNatural: imagem?.naturalWidth,
densidade: window.devicePixelRatio,
});
- Confirme en la pestaña de red el archivo transferido y su tamaño. Redimensionar una pestaña ya cargada puede reutilizar el candidato más grande de la caché; por eso la prueba empieza con el perfil elegido y una carga nueva.
- Ejecute la medición de rendimiento en la página que más concentra imágenes. En este proyecto es
npm run test:lighthouse, con atención primero a la portada.
El resultado esperado no es “800 píxeles en cualquier celular”. Es un candidato coherente con el espacio, la densidad y las condiciones que el navegador evaluó, sin sacrificar nitidez ni transferir 1.200 píxeles por falta de alternativa.
Los errores que hacen que WebP siga pesado
- Ofrecer variantes sin un
sizesfiel. El navegador conoce los archivos, pero recibe una descripción equivocada del espacio. - Usar un descriptor que no corresponde al archivo.
800wtiene que significar 800 píxeles naturales. - Aplicar
loading="lazy"al LCP probable. El ahorro de prioridad se vuelve retraso visible. - Marcar todas las imágenes como prioridad alta. La página pierde la diferenciación entre destacado y contenido secundario.
- Probar solo redimensionando una pestaña con caché. El recurso ya elegido puede enmascarar la configuración nueva.
- Copiar 800 y 1.200 como números universales. Los candidatos tienen que nacer del diseño, de las densidades relevantes y de las imágenes del proyecto.
Las mediciones de LCP de este caso vinieron de un perfil de laboratorio fijo, no de una comparación de usuarios reales. Otra portada, otra imagen u otra distribución de dispositivos puede exigir tres o más anchos. Si la composición de la imagen también tiene que cambiar en el celular, el problema pasa a ser de dirección de arte, normalmente implementada en HTML con <picture>.
WebP puede compactar cada candidato. srcset ofrece las alternativas. sizes describe el espacio. La medición muestra si el navegador recibió información suficiente para elegir bien.
Fuentes
- Reprobación de la compuerta en 2.723 ms, reproducción local de 2.793 a 2.122 ms, elección de la variante de 800 píxeles y perfil de 412 px con densidad 1,75 — extracto público del registro versionado (registro y código del proyecto), verificado el 2026-08-21.
- Versiones del caso y de la nueva revisión: Astro 7.1.1, Astro 7.2.2, ejecución local en Node.js 24.15.0 y requisito Node.js 22.12.0 o superior — extracto público de las versiones registradas (código del proyecto), verificado el 2026-08-21.
- Generación de
card.webpycard-800.webpcon Sharp — extracto público de la transformación usada por el generador (código del proyecto), verificado el 2026-08-21. - Tres valores de
sizesalineados al CSS actual, prioridad del destacado y carga diferida de las demás portadas — extracto público de la implementación de la portada (código del proyecto), verificado el 2026-08-21. - Medición propia de los 17 pares publicados antes de este post: suma del tamaño en bytes de cada
card.webpycard-800.webp, con resultado de 939.640 y 426.294 bytes — inventario público con los 17 pares y la fórmula (archivos del proyecto), medido el 2026-08-21; los valores representan la colección, no el tráfico de usuarios. - Modelo de
srcsetysizes, ancho previsto del espacio y normalización de la densidad de los candidatos — HTML Living Standard: imágenes (estándar oficial), verificado el 2026-08-21. - Uso de
srcset,sizes, ancho del dispositivo y densidad de la pantalla en la selección — web.dev: imágenes responsivas (documentación oficial), verificado el 2026-08-21. - Imágenes en
public/sin procesamiento, generación desrcsetysizesconlayouty necesidad de estilos responsivos — documentación del comportamiento responsivo en Astro y configuraciónimage.responsiveStyles(documentación oficial), verificado el 2026-08-21. - Recomendación de no aplicar carga diferida a la imagen de LCP y de limitar
fetchpriority="high"a las imágenes realmente prioritarias — web.dev: optimizar el LCP (documentación oficial), verificado el 2026-08-21. - Definición formal del LCP como la renderización del mayor elemento de contenido elegible en el área visible — W3C: Largest Contentful Paint (especificación oficial), verificado el 2026-08-21.
- Uso de
currentSrcpara identificar el recurso seleccionado por el navegador — HTML Living Standard:currentSrc(estándar oficial), verificado el 2026-08-21. - Prueba con perfil de dispositivo, recarga y caché deshabilitada — Chrome DevTools: Device Mode y Network panel (documentación oficial), verificado el 2026-08-21.
- Uso de
alt=""en una imagen decorativa cuya información ya aparece en un texto cercano — W3C WAI: Decorative Images (orientación oficial), verificado el 2026-08-21.



