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.
Una etiqueta <img> escrita a mano es una promesa sin garantías: sirve el fichero original tal cual, en el formato en que lo guardaste, al tamaño en que lo exportaste y sin reservar un solo píxel de espacio mientras carga. En una página real eso se traduce en megabytes de más y en saltos de maquetación que empujan el texto cuando la imagen por fin aparece. astro:assets invierte esa lógica: trata cada imagen como un activo del build, un módulo que Astro conoce, mide y transforma antes de servirlo. El componente <Image /> es la puerta de entrada a ese sistema, y su contrato arranca de una idea simple: para optimizar una imagen hay que conocerla.
- Importar imágenes locales desde
srccomo módulos y entender por qué el import es la clave. - Usar
<Image />con sualtobligatorio y unas dimensiones siempre conocidas. - Controlar el formato de salida y la calidad de la transformación.
- Reconocer la carga diferida por defecto y por qué el
srclocal exige un import.
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.
---
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.
---
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.
<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.
---
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.
flowchart LR IMP[import de la imagen local] --> META[Astro lee ancho alto y formato] META --> IMG[componente Image en la plantilla] IMG --> OPT[redimension y reencode en el build] OPT --> OUT[img con formato moderno y espacio reservado] style IMP fill:#89b4fa,color:#11111b style OUT fill:#a6e3a1,color:#11111b
Si colocas una imagen en public y la referencias por su ruta con un <img> a mano, Astro no la toca: sale con su formato y su peso originales, sin dimensiones inyectadas y sin transformación posible. public es para lo que debe salir intacto —un favicon, un fichero que otros descargan por URL fija—, no para las imágenes de tu contenido. La regla práctica es rotunda: si quieres que Astro la optimice, la imagen vive en src y se importa.
Un SVG no es un mapa de bits: no se redimensiona ni se reencoda, porque es vectorial y escala solo sin perder nitidez. Astro lo refleja tratándolos aparte —puedes importarlos y usarlos incluso como componentes— en lugar de pasarlos por el mismo molde de reencode que un png o un jpg. La optimización de la que habla este nivel es la de las imágenes rasterizadas; para los iconos vectoriales, el trabajo es de otra índole.
La convención de src/assets es cómoda, pero no una obligación: puedes importar una imagen desde cualquier carpeta dentro de src, incluso junto al componente o la página que la usa. Colocar cada imagen al lado de quien la consume —lo que se llama co-locación— hace el proyecto más fácil de mover y de borrar sin cabos sueltos. Lo único que Astro exige es que esté en src y que la importes; la carpeta exacta la eliges tú.
El import
Importar la imagen desde src la convierte en un módulo con ancho, alto y formato, no en una cadena de texto.
Las dimensiones
Astro inyecta width y height desde los metadatos del fichero, y con ellos el navegador reserva el espacio.
El alt manda
Sin alt no hay build. La accesibilidad pasa a ser parte del contrato, no un descuido posible.
Perezoso por defecto
loading lazy y decoding async vienen activados: la imagen no bloquea ni se descarga antes de tiempo.
El giro conceptual que propone astro:assets es más profundo que ahorrar unos kilobytes, y merece enunciarse con calma. Durante décadas la web trató las imágenes como recursos opacos: una cadena de texto en un atributo, una URL que apuntaba a un fichero cuyo peso, formato y dimensiones el framework ignoraba por completo. Sobre esa opacidad no se puede razonar, y por eso todo el trabajo de optimización recaía en la persona: exportar el tamaño justo, elegir el formato adecuado, acordarse de anotar el ancho y el alto para evitar el salto de maquetación. Cada imagen era una ocasión de equivocarse, y multiplicada por un sitio entero, una fuente inagotable de páginas lentas. Lo que hace el import es convertir esa opacidad en transparencia: al traer la imagen como un módulo, Astro conoce sus metadatos y gana el poder de actuar sobre ella —medirla, redimensionarla, reencodearla, reservarle sitio— sin pedirte permiso para cada paso. La imagen deja de ser una referencia externa que el sitio arrastra y pasa a ser un dato interno que el build gestiona, con la misma naturalidad con que gestiona un componente o una hoja de estilos. Esa es la idea que recorre todo el nivel y que conviene fijar desde ahora: la optimización no es un filtro que se aplica al final sobre ficheros ciegos, sino una consecuencia de tratar cada activo como un dato tipado del que el sistema lo sabe todo. Cuando conoces una imagen puedes optimizarla; cuando solo tienes su URL, únicamente puedes confiar en que alguien la optimizó antes por ti. astro:assets elige lo primero, y por eso el import —ese gesto minúsculo de escribir una línea arriba en lugar de una ruta abajo— es la pieza sobre la que descansa todo lo demás.
- Coloca una foto en
src/assets, impórtala en una página y píntala con<Image />pasándole solosrcyalt; inspecciona el HTML y comprueba que el<img>salió conwidthyheightque tú no escribiste. - Mueve esa misma foto a
public, refiérela con un<img>a mano y compara peso y formato del recurso servido: verás que Astro no la ha tocado. - Añade
format="avif"y unaqualitybaja, y contrasta el tamaño del fichero resultante con el original. - Usa
import.meta.globpara elegir una imagen local por su nombre en tiempo de ejecución y píntala con<Image />, razonando por qué una ruta en una cadena de texto no habría funcionado.