Rendimiento: CLS, carga y pipeline
La lección que cierra el nivel reúne las decisiones de rendimiento que las imágenes gobiernan: cómo las dimensiones conocidas eliminan el salto de maquetación reservando el espacio con aspect-ratio, cuándo cargar con loading eager y fetchpriority en lugar de lazy, qué aporta decoding async, cómo la prop layout genera imágenes responsive sin fontanería a mano, por qué un sizes honesto ahorra bytes, y en qué se diferencia optimizar en el build frente a hacerlo bajo demanda en cada petición.
Las imágenes son, en casi cualquier sitio, el activo más pesado y el que más daña la experiencia si se maneja mal: hinchan la descarga, retrasan el pintado y provocan esos saltos en los que el texto que ibas a leer se escapa hacia abajo. Todo el nivel ha ido preparando el terreno para esta lección, que reúne las decisiones de rendimiento que las imágenes gobiernan y muestra cómo Astro convierte las buenas prácticas en el camino por defecto. El hilo es uno: porque Astro conoce cada imagen —sus dimensiones, sus formatos, sus tamaños—, puede hornear la corrección en la salida en lugar de dejarla a tu memoria.
- Entender cómo las dimensiones conocidas eliminan el salto de maquetación reservando el espacio.
- Elegir entre
loadingdiferido e inmediato confetchprioritysegún la posición. - Reconocer el papel de
decodingasíncrono y de las imágenes responsive conlayoutysizes. - Distinguir el pipeline de optimización en el build frente a bajo demanda en cada petición.
CLS: reservar el espacio antes de que llegue el píxel
El salto de maquetación acumulado —el CLS— mide cuánto se recoloca el contenido mientras la página carga, y las imágenes sin dimensiones son su causa más común: el navegador no sabe cuánto ocupará la imagen hasta descargarla, así que al principio le da cero altura, coloca el texto pegado y luego, cuando la imagen llega, lo empuja de golpe. Es una de las peores sensaciones de la web, y se evita con un dato: el ancho y el alto.
Conviene situar esto en el mapa de las métricas que se conocen como Core Web Vitals, porque las imágenes tocan las dos que más pesan. El CLS mide la estabilidad visual, y acabamos de ver que unas dimensiones lo llevan a cero. El LCP —el tiempo hasta que se pinta el mayor elemento visible— suele estar protagonizado por una imagen, la de la cabecera, de modo que su formato, su peso y su prioridad de carga deciden buena parte de esa cifra. Optimizar imágenes no es, pues, un retoque cosmético: es actuar directamente sobre las dos señales con las que se juzga la velocidad percibida de una página.
Aquí encaja todo lo del nivel. Como <Image /> conoce las dimensiones —del import, del image() de la colección o de lo que declaraste en una remota—, emite los atributos width y height, y con ellos el navegador reserva la caja exacta antes de descargar un solo byte. El hueco está guardado desde el primer instante, y cuando la imagen aparece encaja en su sitio sin mover nada. La optimización de peso ahorra bytes; la reserva de espacio ahorra saltos, y esta segunda es, para quien lee, la más visible de las dos.
El mecanismo por debajo es una propiedad de CSS, aspect-ratio, que Astro deriva de las dimensiones y aplica a la imagen. Gracias a ella el hueco reservado no es rígido: se adapta al ancho disponible manteniendo la proporción, de modo que la reserva funciona igual en una columna estrecha de móvil que en una ancha de escritorio. Por eso las variantes responsive no reintroducen el salto: todas comparten la misma relación de aspecto, y el navegador conoce la forma de la caja mucho antes de saber qué variante concreta descargará.
loading y decoding: qué se carga ya y qué puede esperar
No todas las imágenes merecen la misma prisa. La que domina la parte alta de la página —la que probablemente sea el mayor elemento visible, el que marca la métrica de LCP— debe empezar a descargarse de inmediato. Las que viven más abajo pueden esperar a que el usuario se acerque a ellas. Astro pone loading="lazy" por defecto, que es lo correcto para la mayoría, pero para la protagonista querrás lo contrario.
---
import { Image } from 'astro:assets';
import portada from '../assets/portada.jpg';
---
<Image
src={portada}
alt="Portada del artículo"
loading="eager"
fetchpriority="high"
/>
loading="eager" le dice al navegador que no espere, y fetchpriority="high" la asciende por delante del resto de descargas. Juntos aseguran que la imagen que define la primera impresión llegue cuanto antes. El decoding="async" que viene por defecto completa el cuadro: permite que el navegador descodifique la imagen sin bloquear el hilo que pinta el resto, de modo que decodificar una foto grande nunca congela la interfaz.
Hay un matiz de disciplina en esto. Promover una imagen con fetchpriority="high" solo ayuda si es de verdad la única protagonista. Si marcas media docena como prioritarias, compiten entre sí y la prioridad deja de significar nada, porque el navegador solo puede adelantar a unas pocas antes de saturar la red. La regla es promover una, la que domina el primer pantallazo, y dejar que el resto siga el orden natural. La prioridad es un recurso escaso y se diluye al repartirlo.
Para exprimir aún más ese primer pintado, la imagen protagonista admite una ayuda extra: precargarla desde el <head>. Una etiqueta <link rel="preload" as="image"> con su href y su imagesrcset le pide al navegador que la busque antes incluso de topar con el <img> en el cuerpo del documento. Es artillería pesada, reservada solo para esa única imagen crítica; usarla en varias volvería a diluir la prioridad que pretendía concentrar.
Merece la pena entender por qué Astro no se limita a confiar en el lazy automático que el navegador ya trae. Ese diferido nativo es una heurística conservadora: el navegador decide por su cuenta qué posterga, y no sabe cuál de tus imágenes es la protagonista. Al exponer loading y fetchpriority como props, Astro te devuelve el control de esa decisión, que es justo la que ninguna heurística acierta sin conocer tu diseño. La carga diferida por defecto es un buen punto de partida; los dos ajustes que la matizan son tuyos.
El antipatrón de rendimiento más caro con imágenes es dejar el loading="lazy" por defecto en la imagen principal de la parte alta. Al diferirla, el navegador la descubre tarde y la descarga tarde, y como suele ser el mayor elemento visible, tu métrica de LCP se hunde justo por la imagen que más importa. La regla es simple y vale oro: la imagen del pliegue superior va siempre con loading="eager" y, si puedes, fetchpriority="high"; el lazy es para todo lo demás.
Responsive con layout: escalar sin fontanería a mano
Servir la misma imagen enorme a un móvil y a un monitor desperdicia datos en el primero. La solución responsive —varias anchuras y un sizes que informe del hueco— ya la viste con <Picture />, pero escribirla a mano en cada imagen cansa. La prop layout la automatiza: al declarar cómo se comporta la imagen en la maqueta, Astro genera por ti el srcset de anchuras y el sizes correspondiente, y añade los estilos para que escale bien.
---
import { Image } from 'astro:assets';
import foto from '../assets/foto.jpg';
---
<Image src={foto} layout="constrained" width={800} alt="Paisaje" />
Con layout="constrained" la imagen se encoge para caber en su contenedor pero nunca se amplía más allá de su tamaño real; full-width la extiende al ancho completo, ideal para una banda de portada; fixed mantiene un tamaño constante. Puedes fijar un layout por defecto para todo el proyecto en image.layout, y a partir de ahí cada imagen es responsive sin que escribas su srcset. Es la misma filosofía del nivel: la práctica correcta como comportamiento por defecto, no como esfuerzo repetido.
Un detalle evita recortes feos cuando el hueco de destino tiene una relación de aspecto distinta de la imagen: las props fit y position deciden cómo encaja —si se recorta para llenar o se ajusta para caber, y por qué borde se alinea—. Son el equivalente en el componente a las propiedades object-fit y object-position de CSS, expuestas como props para que el recorte forme parte de la misma declaración que gobierna el tamaño, y no de una hoja de estilos aparte.
Con imágenes responsive, el navegador elige la variante antes de haber maquetado la página, así que no sabe cuánto espacio ocupará la imagen salvo que se lo digas con sizes. Un sizes mentiroso —declarar 100vw cuando en escritorio ocupa un tercio— hace que descargue de más y tira por la borda el ahorro. Cuando usas layout, Astro propone un sizes razonable; cuando escribes el tuyo, procura que describa el hueco real en cada punto de ruptura. Un sizes honesto es la diferencia entre servir los píxeles justos y malgastarlos.
El pipeline: en el build o bajo demanda
Queda una pregunta que lo atraviesa todo: cuándo ocurre la transformación. La respuesta depende del modo de la ruta, y entenderla evita sorpresas de despliegue. Para una página estática, horneada en el build, las imágenes se optimizan una sola vez durante la construcción: Astro escribe los ficheros ya transformados en el directorio _astro con un nombre que incluye un hash de su contenido, lo que permite servirlos con una caché inmutable de larguísima vida. El coste se paga una vez, al construir, y en tiempo de ejecución no hay trabajo alguno.
Para una página servida bajo demanda, el trabajo se traslada a la petición: un endpoint interno, _image, recibe los parámetros de la transformación —el origen, el ancho, el formato— y produce la imagen en el momento en que se pide. Esto es lo adecuado cuando la imagen no se conoce hasta el instante de servir, pero traslada el coste a cada visita, así que conviene cachear esas respuestas en un CDN para no reoptimizar lo mismo una y otra vez.
Ese hash en el nombre del fichero no es un adorno: es lo que hace segura la caché eterna. Como el nombre cambia si cambia el contenido, el navegador puede guardar cada imagen para siempre sin miedo a servir una versión vieja, porque una edición produce un nombre nuevo que se descarga como un recurso distinto. A cambio, optimizar en el build tiene un coste que crece con el número de imágenes y de variantes: un sitio con miles de fotos alarga su construcción. Ahí es donde el modo bajo demanda reparte la carga, optimizando solo lo que de verdad se pide, cuando se pide.
Hay además una economía silenciosa en cómo Astro genera las variantes: no fabrica combinaciones que nadie pide. Solo emite los formatos, anchuras y calidades que tus componentes de verdad solicitan, y si dos usos coinciden en la misma transformación, comparten el fichero resultante en lugar de duplicarlo. Esa deduplicación mantiene el directorio de salida y el tiempo de build proporcionados al uso real, no al número teórico de combinaciones posibles.
Ese endpoint _image del modo bajo demanda merece una mirada, porque su forma explica cómo se cachea. La transformación viaja en la propia URL como parámetros de consulta —el activo de origen, el ancho, el formato, la calidad—, de modo que cada combinación distinta es una URL distinta y, por tanto, una entrada de caché distinta. Un CDN colocado delante puede guardar cada variante por su URL y responder las siguientes peticiones sin volver a molestar a tu servidor: la primera visita paga la transformación y las demás la heredan gratis.
En un proyecto real rara vez es todo o nada. El renderizado híbrido permite hornear en el build las páginas estables y servir bajo demanda solo las que lo requieren, y las imágenes de cada una siguen la suerte de su página: las de una ruta prerenderizada se optimizan al construir, las de una dinámica en su petición. Elegir el modo de cada ruta es, por tanto, elegir también cuándo y dónde se paga el coste de optimizar sus imágenes, una palanca fina para equilibrar el tiempo de build contra el trabajo en tiempo de ejecución.
flowchart TD IMG[uso de Image en una pagina] --> MODE[modo de la ruta] MODE --> ST[estatica horneada en el build] MODE --> OD[servida bajo demanda] ST --> ASSET[ficheros optimizados en _astro con hash] OD --> EP[endpoint _image transforma en la peticion] ASSET --> CACHE[cache inmutable de larga vida] EP --> CDN[conviene cachear la respuesta en un CDN] style IMG fill:#89b4fa,color:#11111b style MODE fill:#f9e2af,color:#11111b style CACHE fill:#a6e3a1,color:#11111b style CDN fill:#a6e3a1,color:#11111b
Lo esencial es que tu código no cambia entre ambos mundos: escribes el mismo <Image /> y Astro decide dónde y cuándo hacer el trabajo según el modo de la ruta. Solo se mueve el momento del coste —al build o a la petición—, no la forma de pedir la optimización.
Ninguna afirmación sobre rendimiento vale lo que una medición. Abre las herramientas del navegador, mira la pestaña de red y comprueba qué formato, qué peso y qué variante se descargan de verdad en cada tamaño de pantalla; ejecuta luego una auditoría de rendimiento y observa las cifras de LCP y de CLS antes y después de tus cambios. El pipeline de imágenes de Astro pone los cimientos, pero solo la medición confirma que la página se sostiene sobre ellos.
Dimensiones contra el CLS
width y height reservan la caja por su relación de aspecto, y el contenido deja de saltar cuando la imagen llega.
La protagonista, eager
La imagen del pliegue superior va con loading eager y fetchpriority high; el lazy por defecto es para el resto.
layout responsive
Declaras cómo se comporta en la maqueta y Astro genera el srcset y el sizes sin que los escribas a mano.
Build o bajo demanda
En estático se optimiza una vez y se cachea inmutable; bajo demanda se transforma en la petición y se cachea en CDN.
Al cerrar el nivel conviene nombrar lo que de verdad lo unifica, porque no es una lista de trucos de imágenes sino una tesis sobre cómo se logra un sitio rápido. La forma ingenua de perseguir el rendimiento es la vigilancia: acordarse de exportar el tamaño justo, de elegir el formato moderno, de anotar el ancho y el alto, de no diferir la imagen principal, de generar las variantes responsive. Cada una de esas tareas es fácil de olvidar, y multiplicadas por las imágenes de un sitio y por las manos que lo tocan, la probabilidad de que todas se hagan siempre bien tiende a cero. Un sitio que depende de que nadie falle nunca está, en la práctica, condenado a ser lento en algún rincón. Lo que hace astro:assets, y lo que este nivel ha ido revelando pieza a pieza, es cambiar la estrategia de raíz: en lugar de recordarte las buenas prácticas, las hornea en la salida. Porque Astro conoce cada imagen desde el import, puede reservar su espacio sin que anotes las dimensiones, servir un formato moderno sin que lo pidas, generar las variantes responsive sin que escribas un srcset, y diferir por defecto lo que debe esperar. La corrección deja de ser el resultado de cientos de decisiones acertadas y pasa a ser una propiedad del sistema: el camino rápido es, sencillamente, el camino que sigues cuando no haces nada especial. Este es el mismo principio profundo que has visto ya en otros niveles —hacer inevitable lo correcto en lugar de suplicarlo— aplicado ahora a los bytes y los milisegundos. Y su corolario sobre el pipeline es igual de instructivo: que el trabajo ocurra en el build o en la petición no altera la garantía, solo el instante en que se paga; la política —optimiza esta imagen bien— vive separada del mecanismo —hazlo ahora al construir o luego al servir—, y por eso el mismo código rinde en un sitio estático y en uno dinámico. Interioriza esta idea y trascenderá a las imágenes: el rendimiento sostenible no se consigue esforzándose más en cada pieza, sino diseñando el sistema para que la pieza rápida sea la que sale por defecto. Cuando lo correcto es lo que ocurre sin querer, el rendimiento deja de ser una disciplina y se vuelve una consecuencia.
- Toma una página con una imagen grande sin dimensiones, mide su CLS, y comprueba cómo cae a cero al servirla con
<Image />y suswidthyheight. - Marca la imagen de la parte alta con
loading="eager"yfetchpriority="high", deja el resto enlazy, y observa en las herramientas de red el cambio en el orden de descarga. - Aplica
layout="constrained"a una imagen, inspecciona elsrcsety elsizesque Astro generó solo, y reduce la ventana para ver qué variante baja. - Construye el sitio en modo estático y localiza los ficheros optimizados en
_astro; luego sírvelo bajo demanda y observa las peticiones al endpoint_image, razonando cuándo conviene cada modo.