Saltar al contenido

astro:assets y el componente Image

Por qué una etiqueta img cruda es un pasivo de rendimiento y cómo astro:assets la convierte en un activo del build: importar imágenes locales desde src como módulos tipados, el papel de unas dimensiones siempre conocidas frente al salto de maquetación, el alt obligatorio, el control de formato y calidad, la carga diferida y el decodificado asíncrono por defecto, y por qué el src local exige un import analizable y no una ruta cualquiera.

De la etiqueta cruda al activo del build

Astro distingue dos mundos para una imagen, y elegir mal el mundo condena todo lo demás. Un fichero en public se copia al sitio verbatim: Astro no lo mira, no lo mide y no lo toca, así que sale al navegador exactamente como lo dejaste. Un fichero en src —por convención en src/assets— es otra cosa: al importarlo se convierte en un módulo que el compilador entiende, con sus metadatos a mano y su contenido disponible para transformar. La primera decisión, entonces, no es qué prop pasar, sino dónde vive la imagen.

Ese import no devuelve una cadena de texto con una ruta, sino un objeto con la forma de un ImageMetadata: su src, su width, su height y su format. Esa es la materia prima que el componente necesita.

ASTRO
---import { Image } from 'astro:assets';import hero from '../assets/hero.png';---<Image src={hero} alt="Vista aérea del puerto al amanecer" />

Fíjate en lo que no está escrito: no hay width ni height en la etiqueta, y sin embargo el <img> que sale al HTML los lleva. Los ha aportado el import: al leer el fichero, Astro conoció sus dimensiones reales y las inyectó por ti. La imagen dejó de ser una URL opaca y pasó a ser un dato con estructura, y sobre un dato con estructura sí se puede razonar.

Ese vínculo tiene una consecuencia práctica que conviene nombrar: la imagen entra a formar parte del grafo de dependencias del build. Astro sabe qué páginas usan qué activo, de modo que si editas el fichero se reconstruye lo que depende de él, y si nadie lo importa no se procesa ni se emite. La imagen deja de ser un fichero suelto que arrastras a mano y pasa a ser una dependencia rastreada, con la misma naturalidad con que lo es un módulo de código.

Dimensiones siempre conocidas y el alt obligatorio

La regla de oro de astro:assets es que ninguna imagen optimizada viaja sin dimensiones. No es un capricho: conocer el ancho y el alto es lo que permite emitir esos atributos en el <img>, y con ellos el navegador calcula la caja —la relación de aspecto— y reserva el espacio antes de que un solo byte de la imagen llegue. Ese hueco reservado es lo que evita el salto de maquetación, ese tirón feo con el que el contenido se recoloca al aparecer la imagen. Para una imagen local las dimensiones salen gratis del import; en el nivel de imágenes remotas veremos que allí sí hay que declararlas a mano.

Puedes anular esas dimensiones para servir la imagen más pequeña. Si pasas solo uno de los dos valores, Astro calcula el otro conservando la proporción, de modo que nunca la deformas. Lo que no hará es ampliar por encima del tamaño original: pedir más píxeles de los que el fichero tiene solo produciría una imagen borrosa, así que Astro se niega.

Ese rechazo a ampliar responde a una lógica de calidad, no a terquedad. Una imagen optimizada nunca debería verse peor que el original, y estirar un mapa de bits por encima de su resolución real solo inventa píxeles difusos. Si necesitas mostrarla más grande, la salida no es pedirle a Astro que la agrande, sino partir de un fichero de mayor resolución. El componente protege la nitidez negándose a hacer lo que la degradaría.

ASTRO
---import { Image } from 'astro:assets';import retrato from '../assets/retrato.jpg';---<Image src={retrato} width={400} alt="Retrato de la autora" />

El alt merece una nota aparte, porque aquí Astro es más estricto que el HTML. En una etiqueta <img> nativa un alt ausente pasa en silencio; en <Image /> es un error de compilación que detiene el build. La accesibilidad deja de depender de tu disciplina y se convierte en parte del contrato: no hay imagen sin texto alternativo, porque el sitio no compila sin él.

El caso de la imagen decorativa merece precisión, porque el impulso de omitir el alt es justo el error. Una imagen que no aporta información —un adorno, una textura— debe llevar un alt vacío escrito a propósito, alt="", que le indica al lector de pantalla que la ignore sin dudar. La diferencia entre un alt ausente y uno vacío es enorme para quien navega con un lector: el primero lo obliga a adivinar si se pierde algo; el segundo le confirma que no. Astro te fuerza a tomar esa decisión de forma consciente en lugar de dejarla al olvido.

Formato, calidad y carga diferida

Por defecto, el componente no solo redimensiona: reencoda. La salida es webp, un formato moderno que pesa bastante menos que un png o un jpg equivalente y que hoy entienden todos los navegadores relevantes. Si necesitas otro, la prop format lo fuerza, y quality gobierna el equilibrio entre peso y nitidez.

ASTRO
<Image  src={hero}  alt="Vista aérea del puerto al amanecer"  format="avif"  quality="mid"/>

La quality admite tanto números de 0 a 100 como palabras clave —low, mid, high, max— que Astro traduce al valor idóneo de cada formato, porque el número que da buena calidad en webp no es el mismo que en avif. Subir la calidad pesa más y recorta el ahorro, y el punto dulce suele estar por debajo de lo que uno cree, porque los formatos modernos degradan con mucha gracia. Empieza por la calidad media y sube solo si un detalle concreto lo pide.

Conviene mirar una vez el <img> que sale al HTML para perderle el misterio. Lleva el src apuntando al fichero ya optimizado, los width y height que fijan la caja, el loading y el decoding con sus valores, y el alt que escribiste. Ninguno es una sorpresa: son exactamente las decisiones que tomaste, unas por defecto y otras a mano, materializadas en atributos. El componente no esconde nada; solo te ahorra teclearlos y garantiza que ninguno falte.

Faltan dos comportamientos que el componente activa sin que los pidas y que conviene conocer para no pelearte con ellos. loading vale lazy por defecto: la imagen no se descarga hasta que está a punto de entrar en pantalla, lo que ahorra ancho de banda en todo lo que vive más abajo del pliegue. Y decoding vale async: el navegador decodifica la imagen sin bloquear el pintado del resto. Para la imagen protagonista de la parte alta querrás lo contrario —loading="eager"—, y a ese matiz de rendimiento le dedicaremos la última lección del nivel.

El src local exige un import, no una ruta cualquiera

Hay una restricción que sorprende a quien llega de escribir HTML a mano: el src de una imagen local no acepta una cadena con una ruta, solo un import. La razón enlaza con todo lo anterior. Astro necesita conocer la imagen en el momento del build para medirla y transformarla, y un import es analizable estáticamente —el compilador lo ve y resuelve el fichero—, mientras que una ruta calculada en tiempo de ejecución podría apuntar a cualquier cosa y no le diría nada de antemano.

Eso plantea un problema real: cómo eliges entre muchas imágenes locales por un nombre que solo conoces al ejecutar, por ejemplo el de una entrada de una colección. La respuesta es import.meta.glob, que importa un conjunto de ficheros de golpe y te entrega un mapa de funciones que los cargan bajo demanda. Con ese mapa resuelves el import correcto a partir del nombre, sin renunciar al análisis estático.

ASTRO
---import { Image } from 'astro:assets';const imagenes = import.meta.glob('../assets/equipo/*.jpg');const cargar = imagenes['../assets/equipo/ada.jpg'];const { default: foto } = await cargar();---<Image src={foto} alt="Retrato de Ada" />

El mapa que devuelve import.meta.glob contiene, para cada fichero que casó el patrón, una función que lo importa. Eliges la clave que te interesa, la invocas y obtienes el mismo ImageMetadata que un import directo. Así conservas la optimización incluso cuando la imagen se decide con datos, y evitas el error de pasarle a <Image /> una simple ruta que Astro no sabría procesar.