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.
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.
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.
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.
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.