Imágenes remotas y en colecciones
Cómo trata Astro una imagen que no vive en src: por qué una URL remota obliga a declarar width y height, por qué su optimización está desactivada hasta que autorizas el origen con image.domains o image.remotePatterns, qué riesgo cierra esa autorización, y cómo el ayudante image del esquema de una content collection convierte una ruta del frontmatter en un activo local validado y optimizable en el build.
Todo lo anterior partía de un import: la imagen vivía en src, y ese gesto le daba a Astro sus metadatos. Pero muchas imágenes llegan de fuera —de un CDN, de un gestor de contenidos, de una API— como una simple URL. Ahí el import ya no existe, y con él se pierden las dimensiones y la posibilidad de transformar. Astro no se rinde, pero cambia las reglas: para una imagen remota tú aportas las dimensiones que el fichero ya no le regala, y su optimización queda desactivada por defecto hasta que declares que confías en el origen. Y para el caso intermedio —imágenes que acompañan al contenido de una colección— el esquema ofrece un ayudante que devuelve el import perdido.
- Servir una imagen remota con
<Image />y entender por qué exigewidthyheight. - Autorizar orígenes con
image.domainsyimage.remotePatternspara habilitar su optimización. - Comprender qué riesgo de seguridad cierra esa autorización explícita.
- Validar y optimizar imágenes de una content collection con el ayudante
imagedel esquema.
La URL que Astro no conoce
Cuando el src de <Image /> es una cadena con una URL remota, el compilador no tiene el fichero delante: no puede leer su ancho ni su alto sin descargarlo, y no va a descargar medio internet en cada build para averiguarlo. Por eso te traslada a ti esa responsabilidad: en una imagen remota, width y height son obligatorios. No es burocracia, es la misma regla de siempre —ninguna imagen sin dimensiones— aplicada al caso en que Astro no puede deducirlas solo.
---
import { Image } from 'astro:assets';
---
<Image
src="https://images.unsplash.com/photo-123"
width={1200}
height={800}
alt="Muelle al atardecer"
/>
Hay una segunda diferencia, más silenciosa. Por defecto, Astro no optimiza una imagen remota: la deja pasar hacia el navegador tal como está, sin reencodear ni redimensionar, limitándose a poner las dimensiones que declaraste. La razón no es técnica sino de confianza, y la desactivamos a propósito en el siguiente paso.
Existe un atajo para no teclear las dimensiones a mano: la prop inferSize. Con ella, Astro descarga la imagen remota durante el build, lee su ancho y su alto reales y los rellena por ti. Es cómodo, pero no gratis: cada inferSize es una petición de red en tiempo de construcción, así que en un sitio con muchas remotas alarga el build y lo hace depender de que esos orígenes respondan a tiempo. Úsalo cuando no conozcas las dimensiones de antemano, y prefiere declararlas tú cuando sí las sepas.
<Image
src="https://images.unsplash.com/photo-123"
inferSize
alt="Muelle al atardecer"
/>
Autorizar orígenes: domains y remotePatterns
Para que Astro sí transforme una imagen remota tienes que declarar que su origen es de fiar. Hay dos llaves en la configuración. image.domains acepta una lista de nombres de host exactos; cualquier imagen servida desde uno de ellos entra en la maquinaria de optimización. image.remotePatterns es más expresivo: describe patrones por protocolo, host y ruta, y admite comodines para cubrir subdominios enteros.
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
image: {
domains: ['images.unsplash.com'],
remotePatterns: [{ protocol: 'https', hostname: '**.mi-cdn.com' }],
},
});
Con esa declaración, una imagen de images.unsplash.com o de cualquier subdominio de mi-cdn.com pasa a optimizarse como si fuera local: formato moderno, tamaños a medida, todo el arsenal. Las de cualquier otro origen siguen sirviéndose intactas. La diferencia entre una imagen remota optimizada y una que solo cruza el sitio es, literalmente, una línea en la configuración.
Dos trampas conviven aquí. La primera: si esperas que una imagen remota salga en webp y ves que llega en su formato original, casi siempre es que su origen no está en domains ni en remotePatterns; Astro la deja pasar sin tocarla y no te avisa. La segunda: olvidar width o height en una imagen remota es un error de compilación, no un aviso. Ante una imagen externa que no se optimiza, revisa primero la autorización del origen; ante un build que se detiene, revisa que declaraste sus dimensiones.
Por qué la autorización es explícita
Que la optimización remota venga apagada no es pereza de Astro, sino una decisión de seguridad. El servicio de imágenes es, en el fondo, un motor que recibe una URL, descarga lo que haya allí y lo procesa. Si aceptara cualquier URL, se convertiría en un proxy abierto: alguien podría pedirle que fuera a buscar y transformar imágenes de orígenes arbitrarios usando tu servidor como intermediario, consumiendo tu cómputo y tu ancho de banda, o alcanzando destinos que tu servidor no debería tocar. Obligarte a enumerar los orígenes de confianza cierra esa puerta: el motor solo saldrá a buscar donde tú, explícitamente, le has dado permiso.
flowchart TD Q[de donde viene la imagen] --> L[local en src] Q --> R[remota por URL] L --> OPT[Astro la optimiza con las dimensiones del fichero] R --> AUTH[origen declarado en domains o remotePatterns] R --> UNAUTH[origen no declarado] AUTH --> YES[Astro la transforma y optimiza] UNAUTH --> PASS[Astro la sirve tal cual sin tocarla] style Q fill:#89b4fa,color:#11111b style OPT fill:#a6e3a1,color:#11111b style YES fill:#a6e3a1,color:#11111b style PASS fill:#f38ba8,color:#11111b
El ayudante image en el esquema de una colección
Entre la imagen local con import y la remota con URL hay un tercer caso muy común: imágenes que acompañan al contenido de una content collection y se nombran en su frontmatter. Escribir ahí una ruta como cadena de texto no garantiza nada —puede apuntar a un fichero borrado—, así que el esquema ofrece una forma mejor. Escrito en su variante de función, recibe un ayudante image que valida la ruta contra un fichero real y devuelve sus metadatos, exactamente el objeto que <Image /> sabe optimizar.
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
schema: ({ image }) =>
z.object({
title: z.string(),
cover: image(),
coverAlt: z.string(),
}),
});
En el frontmatter de cada entrada, cover es una ruta relativa al propio fichero de la entrada, y image() comprueba en el build que ese activo existe. Si no está, el sitio no compila, igual que faltaría un campo obligatorio. Lo que llega a la plantilla ya no es una cadena frágil, sino el mismo ImageMetadata con ancho, alto y formato que optimizarías desde un import.
---
import { Image } from 'astro:assets';
import { getEntry } from 'astro:content';
const post = await getEntry('blog', 'mi-post');
---
<Image src={post.data.cover} alt={post.data.coverAlt} />
La elección entre image() y una cadena URL es la elección entre un activo gestionado y una promesa externa. Si la portada vive contigo, image() la valida y la optimiza; si es una URL remota que quieres guardar en el frontmatter, usa z.string().url(), asumiendo que renuncias a la validación del fichero y a la optimización salvo que autorices su origen. Emparejar image() con un coverAlt obligatorio, como ya viste con las collections, convierte además el texto alternativo en parte del contrato.
Remota con dimensiones
Una URL externa no trae metadatos, así que width y height pasan a ser tuyos y son obligatorios.
Autorizar el origen
domains enumera hosts exactos; remotePatterns describe patrones con comodines. Sin ellos no hay optimización.
Cerrar el proxy abierto
La autorización explícita impide que tu servicio de imágenes descargue y transforme cualquier URL del mundo.
image en el esquema
En una colección, image valida la ruta contra un fichero real y devuelve el activo listo para optimizar.
Detrás de esas dos claves de configuración late un principio que gobierna toda la seguridad de un sistema conectado, y vale la pena sacarlo a la luz. Cualquier componente que, a petición de un tercero, sale a la red a buscar un recurso y lo procesa es un arma de doble filo: la misma potencia que lo hace útil lo hace peligroso si no se acota. Un optimizador de imágenes que aceptara cualquier URL sería exactamente eso —un servidor tuyo dispuesto a ir a donde le manden y traer lo que le pidan—, y esa disposición, aparentemente inocente, es la raíz de toda una familia de abusos: usar tu infraestructura como pantalla para alcanzar destinos ajenos, agotar tu cómputo con transformaciones que nadie pidió, convertir tu servicio en un eslabón de un ataque contra otros. La respuesta madura no es prohibir la potencia, sino enmarcarla: declarar de antemano el conjunto cerrado de orígenes en los que confías, de modo que la capacidad exista solo dentro de una frontera que tú has trazado. Eso es justo lo que hacen domains y remotePatterns, y por eso vienen vacías por defecto: un sistema bien diseñado no concede confianza de forma implícita, la exige por escrito. La lección trasciende con mucho a las imágenes. Cada vez que tu código actúa sobre entradas que otro controla —una URL que buscar, una plantilla que renderizar, una consulta que ejecutar— estás ante la misma pregunta: cuál es el conjunto exacto de lo permitido, y cómo lo declaro para que todo lo demás quede fuera. La seguridad, en su forma más honesta, no es una capa que se añade al final, sino este hábito de nombrar explícitamente la frontera de lo confiable y negar por defecto cuanto queda al otro lado. Autorizar un dominio de imágenes es un ejercicio pequeño y concreto de esa disciplina, y quien aprende a verlo así lo reconocerá luego en cada límite que su sistema tenga con el mundo.
- Pinta una imagen remota con
<Image />sin declararwidthniheighty observa que el build se detiene; añádelos y comprueba que ya compila. - Verifica en el HTML que esa imagen remota sale sin optimizar; luego añade su host a
image.domains, reconstruye y confirma que ahora se sirve en formato moderno. - Sustituye el
domainspor unremotePatternscon un comodín de subdominio y comprueba que sigue funcionando para varias rutas del mismo CDN. - Declara un campo
coverconimage()en el esquema de una colección, apúntalo a un fichero inexistente para ver fallar el build, corrígelo y píntalo con<Image />usandocoverAltcomo texto alternativo.