Escribir un loader inline: fetch a una API
El primer loader propio es una simple función asíncrona que devuelve un array de entradas con id. Cómo traer datos de una API remota con fetch, transformarlos con map, dejar que el esquema los valide y reconocer los límites que empujan hacia el loader como objeto en Astro 7.
El primer loader propio que escribirás no es un objeto con métodos ni una pieza reutilizable: es una simple función asíncrona. Le pasas a loader una función que devuelve un array de entradas, y Astro se encarga del resto —validar, tipar, guardar en el store—. Es la forma más directa de traer contenido de donde glob y file no llegan: una API remota, una base de datos, un servicio. Con unas pocas líneas, una colección tipada puede alimentarse de datos que viven a mil kilómetros de tu disco.
- Escribir un loader inline como una función asíncrona que devuelve un array de entradas.
- Traer datos de una API remota con
fetchy transformarlos en entradas conid. - Entender qué hace Astro con el array que devuelves y cómo lo valida contra el esquema.
- Reconocer los límites del loader inline y cuándo pedirá dar el salto al loader como objeto.
El loader inline: una función que devuelve entradas
En su forma más simple, un loader es una función asíncrona sin argumentos que devuelve un array. Cada elemento del array es una entrada, y la única regla es que cada uno traiga un campo id único. Astro toma ese array, valida cada elemento contra el esquema y lo deposita en el store. No hay que aprender ninguna API nueva: si sabes escribir una función que devuelve datos, sabes escribir un loader inline.
import { defineCollection, z } from 'astro:content';
const productos = defineCollection({
loader: async () => {
return [
{ id: 'teclado', nombre: 'Teclado', precio: 80 },
{ id: 'raton', nombre: 'Raton', precio: 30 },
];
},
schema: z.object({
id: z.string(),
nombre: z.string(),
precio: z.number(),
}),
});
export const collections = { productos };
Ese array escrito a mano ilustra la forma, pero desaprovecha el poder: si los datos van a estar en el código, mejor un file. El loader inline brilla cuando el array no lo escribes tú, sino que lo traes de una fuente que ninguno de los loaders de fábrica sabe leer. La función es el hueco donde cabe cualquier lógica —una llamada de red, una consulta, una transformación— con tal de que termine devolviendo entradas con id.
Astro también acepta que, en vez de un array, devuelvas un objeto cuyas claves sean los id y cuyos valores sean los datos. Las dos formas son equivalentes; el array es más natural cuando la fuente ya te da una lista, y el mapa lo es cuando trabajas con pares clave-valor. Elijas la que elijas, el resultado en el store es el mismo: un conjunto de entradas identificadas por su id.
Devolver un mapa tiene, además, una comodidad: el id es la clave, así que no hace falta repetirlo dentro de cada objeto. Cuando la fuente ya te da un identificador natural, construir ese mapa es directo.
loader: async () => {
const res = await fetch('https://api.ejemplo.com/autores');
const lista = await res.json();
return Object.fromEntries(
lista.map((autor) => [autor.slug, { nombre: autor.nombre }]),
);
}
Fetch a una API remota
El caso canónico del loader inline es traer datos de una API. Dentro de la función asíncrona, un fetch normal descarga los datos; después, un map los transforma en la forma que tu esquema espera, cuidando de asignar a cada uno un id. Como la función corre en el build, dentro de Node, dispones de fetch y de todo el entorno del servidor sin restricciones de navegador.
const paises = defineCollection({
loader: async () => {
const res = await fetch('https://restcountries.com/v3.1/all');
if (!res.ok) throw new Error(`La API respondio ${res.status}`);
const datos = await res.json();
return datos.map((pais) => ({
id: pais.cca3,
nombre: pais.name.common,
region: pais.region,
poblacion: pais.population,
}));
},
schema: z.object({
id: z.string(),
nombre: z.string(),
region: z.string(),
poblacion: z.number(),
}),
});
Hay tres detalles que separan un loader inline de juguete de uno serio. El primero es el id: la fuente casi nunca lo llama así, de modo que tu map debe elegir qué campo hace de identidad —aquí cca3, el código de tres letras del país— y exponerlo como id. El segundo es el error: si la respuesta no es correcta, lanzar una excepción detiene el build, que es justo lo que quieres —mejor un build roto que una colección medio vacía y silenciosa—. El tercero es la forma: lo que devuelves debe encajar con el esquema, así que el map es también el lugar donde adaptas los nombres y los tipos de la API a los tuyos.
El id
La fuente rara vez lo llama id. Tu map elige que campo hace de identidad y lo expone como id.
El error
Si la respuesta falla, lanza. Un build roto es mejor que una coleccion medio vacia y silenciosa.
La forma
Lo devuelto debe encajar con el esquema. El map adapta nombres y tipos de la API a los tuyos.
El entorno
La funcion corre en Node durante el build: tienes fetch y todo el servidor, sin limites de navegador.
Una API rara vez entrega un campo llamado id, y muchas entregan varios candidatos: un identificador numérico, un slug, un UUID. Elegir cuál será el id de tu entrada no es trivial, porque de esa elección dependen las referencias y las URLs. Prefiere un identificador estable de la fuente —uno que la API prometa no cambiar— frente a uno volátil como una posición en la lista. Un id derivado del índice del array se rompe en cuanto la fuente reordena sus datos.
Qué hace Astro con lo que devuelves
Entender el ciclo completo ayuda a no tratar el loader inline como una caja negra. Cuando arranca el dev server o el build, Astro invoca tu función, espera a que la promesa resuelva y recibe tu array. Antes de guardarlo, valida cada entrada contra el schema: si una incumple —falta un campo, un tipo no cuadra—, el build se detiene señalando la entrada culpable por su id. Solo las entradas que pasan la validación llegan al store, ya tipadas.
Ese orden —traer, transformar, validar, guardar— es siempre el mismo, y merece grabarse porque reaparecerá con variantes en todos los loaders del nivel. El loader inline lo recorre entero dentro de una sola función anónima; los loaders más avanzados lo despliegan en piezas con más control, pero la coreografía no cambia. Si tienes clara esta secuencia, ya entiendes el esqueleto de cualquier loader, por sofisticado que se vuelva después.
flowchart TD BUILD[arranca el build] --> FN[funcion loader] FN --> FETCH[fetch a la API] FETCH --> MAP[map a entradas con id] MAP --> ARR[array devuelto] ARR --> VAL[validacion contra el schema] VAL -->|cumple| STORE[store poblado] VAL -->|falla| STOP[build detenido] STORE --> API[getCollection tipado] style STORE fill:#a6e3a1,color:#11111b style API fill:#a6e3a1,color:#11111b style STOP fill:#f38ba8,color:#11111b
Como la función se ejecuta en el build dentro de Node, puedes usar secretos —una clave de API en una variable de entorno— sin miedo a filtrarlos: nada de ese código llega al cliente. Lo que viaja al navegador son las páginas ya generadas, no el loader que las alimentó. Esa es una ventaja discreta de cargar en build-time: el punto de contacto con la fuente es privado por construcción, y las credenciales nunca cruzan la frontera hacia el navegador.
Hay una característica del loader inline que conviene tener muy presente: reemplaza el store entero en cada ejecución. La función no sabe qué había antes ni compara con lo previo; simplemente devuelve la lista completa, y Astro la toma como la nueva verdad. Eso es una virtud —es simple y predecible— y una limitación: si la API tiene diez mil entradas y solo cambian tres, el loader inline las trae y las revalida las diez mil, cada build, sin aprovechar nada del trabajo anterior.
Esa limitación es exactamente la frontera donde el loader inline se queda corto y pide convertirse en algo más. Cuando necesites no rehacerlo todo cada vez —consultar qué había, actualizar solo lo que cambió, recordar entre builds un token de sincronización— la función pura ya no basta: hará falta el loader como objeto, con acceso directo al store y a la memoria del build, que es lo que aborda la próxima lección. El loader inline es el escalón perfecto para empezar y para fuentes pequeñas; el objeto es el escalón para la escala y la eficiencia.
El loader inline encierra una lección de diseño que trasciende a Astro: la potencia de empezar por la abstracción más simple que resuelve el problema. Podrías pensar que traer datos remotos a un sistema tan estructurado como las content collections exige una maquinaria imponente —clases, interfaces, ciclos de vida—, y sin embargo el punto de entrada es una función que devuelve un array. Esa modestia es deliberada y sabia. Un buen diseño de API ofrece una rampa, no un muro: te deja resolver el caso fácil con esfuerzo fácil, y solo te pide aprender más cuando tu problema, de verdad, se ha vuelto más difícil. La progresión de este nivel —función inline, luego objeto con store, luego caché incremental— no es una escalera que haya que subir entera, sino un abanico de opciones donde eliges el peldaño que tu caso merece. Cargar con la complejidad del loader como objeto para traer doce productos de una API sería tan desacertado como escribir un bucle manual para leer un blog de trescientos posts: en ambos extremos, el tamaño de la herramienta no se ajusta al del problema. La disciplina del ingeniero maduro no es conocer la solución más sofisticada, sino calibrar cuál es la más simple que aún resuelve el caso, y ascender solo cuando el dolor lo justifica. El loader inline te enseña a habitar ese primer peldaño sin vergüenza y sin sobreingeniería: unas líneas, un fetch, un map, un id, y ya tienes contenido remoto tan tipado como el local. Guarda la artillería pesada para cuando el enemigo la merezca.
- Escribe un loader inline que haga
fetcha una API pública y devuelva un array de entradas, cada una con unidestable tomado de la fuente. - Añade un
schemaque valide esas entradas y provoca a propósito un desajuste de tipo para ver el build detenerse con elidseñalado. - Cambia el
idpara que salga de la posición en el array —el índice— y razona por qué esa elección es frágil frente a un identificador de la fuente. - Estima cuántas entradas trae tu API y decide, con argumentos, si el loader inline te basta o si vas a necesitar el loader como objeto.