Qué son las content collections y por qué
El salto de leer ficheros de contenido a mano —frágil, sin tipos, con errores que estallan en producción— a las content collections: grupos de contenido tipado y validado en build. El problema que resuelven y su lugar en la Content Layer API de Astro 7.
Un sitio con contenido —un blog, una documentación, un catálogo— es, en el fondo, una pequeña base de datos escrita en ficheros. La pregunta es cómo la lees. Puedes recorrer una carpeta a mano, parsear cada frontmatter y confiar en que todos los campos existen y tienen el tipo correcto; o puedes declarar una vez la forma de ese contenido y dejar que Astro lo descubra, lo valide y te lo entregue tipado. Las content collections son lo segundo: contenido tratado como datos con contrato, no como texto suelto que esperas que salga bien.
- Reconocer la fragilidad de leer ficheros de contenido a mano, sin tipos ni validación.
- Entender una content collection como un grupo de contenido tipado y validado.
- Ver cómo la validación en tiempo de build transforma los errores de datos en errores tempranos.
- Situar las collections dentro de la Content Layer API de Astro 7.
El contenido leído a mano y sus grietas
Sin collections, cargar un blog es un ejercicio de arqueología manual. Recorres la carpeta con import.meta.glob o con el sistema de ficheros, extraes el frontmatter de cada Markdown y te quedas con un objeto cuyo tipo es, en el mejor de los casos, any. Nada garantiza que title exista, que pubDate sea una fecha y no una cadena, o que no hayas escrito puDate en un fichero de cien. El compilador calla porque no tiene forma de saber qué prometiste que habría dentro.
Ese silencio es lo más peligroso. Un lenguaje dinámico no distingue entre un objeto bien formado y uno corrupto: ambos son, para él, simplemente objetos. Así que un frontmatter incompleto viaja feliz por tu código, sin protestar, propagándose de función en función hasta que alguien intenta usar el campo que falta. Solo entonces, en plena ejecución y lejos del origen, aparece el fallo —y aparece disfrazado, como un undefined que se cuela donde se esperaba una fecha—.
Esa carga manual acumula grietas que solo se ven tarde:
- Sin tipos.
entry.data.titleesany; el editor no autocompleta ni te avisa de un campo inexistente. - Sin validación. Un frontmatter al que le falta un campo obligatorio no falla al guardarlo: falla cuando alguien visita esa página.
- Tipos accidentales. Una fecha escrita entre comillas llega como texto, y
date.getFullYearrevienta en la plantilla. - Errores tardíos. El fallo no aparece donde está el dato roto, sino páginas más allá, en producción, ante el visitante.
// Lectura a mano: sin red de seguridad
const files = import.meta.glob('./posts/*.md', { eager: true });
const posts = Object.values(files);
// posts[0].frontmatter es any: nadie valida title, pubDate ni tags
El problema de fondo no es la comodidad, sino la confianza. Estás tratando datos estructurados como si fueran texto libre, y aplazas la comprobación de su forma hasta el momento más caro para descubrir un error: cuando ya está desplegado y lo encuentra alguien que no eres tú.
Y el coste no crece de forma lineal, sino con el proyecto. Con tres posts, revisar el frontmatter a ojo es viable; con trescientos, es inabarcable, y basta un colaborador que escriba fecha donde tú pusiste pubDate para que una página quede en blanco sin que nadie lo advierta durante semanas. La lectura manual no falla por indisciplina: falla porque encarga a la vigilancia humana una tarea repetitiva que solo una máquina hace sin cansarse ni distraerse.
Nada de esto significa que leer ficheros a mano esté prohibido: para un prototipo de un fin de semana puede bastar. El argumento es de escala y de tiempo. En cuanto el contenido importa —porque son muchos, porque los edita más gente, porque llevan años vivos— la ausencia de un contrato deja de ser una comodidad y se convierte en una fuente constante de fallos silenciosos que se pagan a plazos.
Una colección: contenido tipado y validado
Una content collection invierte el planteamiento. En lugar de leer y rezar, declaras una vez la forma del contenido y Astro se encarga del resto: descubre los ficheros, valida cada uno contra tu esquema y te entrega entradas con un tipo exacto. Una colección es, entonces, un grupo de entradas que comparten una misma forma declarada —un esquema— y que Astro gestiona como una unidad coherente.
La palabra clave es declarar. No escribes el código que recorre la carpeta ni el que valida cada campo; describes el resultado que quieres —esta colección tiene esta forma— y dejas que el framework encuentre el cómo. Ese estilo declarativo es lo que permite que Astro genere los tipos, valide en el build y, el día de mañana, cambie la fuente del contenido sin que tu descripción tenga que enterarse.
El acceso ya no es un glob disperso, sino una API única, astro:content, que devuelve datos tipados:
import { getCollection } from 'astro:content';
const posts = await getCollection('blog');
// posts esta tipado: post.data.title es string, post.data.pubDate es Date
Astro genera los tipos de cada colección a partir de tu esquema y los mantiene sincronizados con él. El editor autocompleta data, marca en rojo un campo que no existe y conoce el tipo de cada valor antes de que ejecutes nada. El contenido dejó de ser un any opaco para convertirse en un objeto con contrato, tan predecible como cualquier otro dato tipado de tu programa.
Conviene fijar aquí el vocabulario, porque reaparecerá durante todo el nivel. Una colección es el grupo entero —el blog—; una entrada, cada uno de sus miembros —un post—; el esquema, la forma que todas comparten; y el loader, que verás en la próxima lección, el lugar de donde salen. Con esas cuatro palabras se describe cualquier colección, por enrevesada que sea su fuente de datos.
Ese acceso tipado no es magia opaca. Astro expone las colecciones a través de un módulo virtual, astro:content, que no existe como fichero en tu disco: lo genera el propio framework a partir de tu configuración. Al arrancar, un paso de sincronización —astro sync, que Astro dispara solo— recorre tus esquemas y emite las definiciones de tipos que hacen que data tenga forma. Por eso el autocompletado aparece sin que instales nada extra: el tipo del contenido se deriva, campo a campo, del esquema que tú escribiste.
Esa derivación automática cierra una brecha clásica: la que separa la documentación de la realidad. Aquí no hay un comentario que describa los campos y que pueda quedar obsoleto; el esquema es, a la vez, la definición, la validación y el tipo. Una sola fuente gobierna las tres facetas, así que nunca podrán contradecirse entre sí.
La validación como red de seguridad
Lo que de verdad cambia el juego es cuándo se comprueba la forma. Con collections, la validación ocurre en el arranque del servidor de desarrollo y en el build, antes de generar una sola página. Si un fichero incumple el esquema —falta un campo, una fecha es inválida, un tipo no cuadra—, Astro detiene el proceso y te dice exactamente qué colección, qué entrada y qué campo fallan.
Ese mensaje es la diferencia entre depurar y no tener que depurar. En lugar de un undefined que aflora tres componentes más allá de su origen, recibes una frase que nombra el fichero y el campo: se esperaba una fecha en pubDate y llegó otra cosa. El error trae su dirección postal, y corregirlo es abrir ese fichero y arreglar ese campo, sin bisectar ni reproducir nada. Y hay un reverso positivo que conviene subrayar: si el build pasa, es que todo el contenido cumple, no una muestra. La compilación es una comprobación exhaustiva, y esa garantía de conjunto —o pasan todas las entradas o no pasa ninguna— es lo que te deja construir encima sin desconfiar de cada dato por separado.
Astro solía leer una carpeta src/content por pura convención. Desde la Content Layer API —el modelo estándar en Astro 7— una colección se define explícitamente con un loader que dice de dónde sale el contenido: ficheros locales, una API remota, un CMS. Las collections dejaron de estar atadas a una carpeta mágica y pasaron a ser un contrato declarado que puede alimentarse de cualquier fuente, siempre con el mismo acceso tipado por encima.
flowchart TD F[ficheros de contenido] --> G[lectura a mano con glob] F --> C[content collection con loader] G --> ANY[datos any sin validar] ANY --> PROD[error en produccion] C --> VAL[validacion en build] VAL --> TIP[datos tipados] TIP --> EARLY[error temprano o exito] style ANY fill:#f38ba8,color:#11111b style PROD fill:#f38ba8,color:#11111b style TIP fill:#a6e3a1,color:#11111b style EARLY fill:#a6e3a1,color:#11111b
La diferencia es de naturaleza, no de grado. Sin collections, un dato roto es una bomba de relojería que estalla en una visita futura; con collections, es un error de compilación que ves en el acto, con el fichero y el campo señalados. Has movido el fallo desde el punto más caro —producción— hasta el más barato —tu terminal, mientras escribes—.
Hay además un dividendo que no se aprecia hasta que se prueba: el editor se vuelve un aliado activo. Al teclear post.data. aparece la lista exacta de campos con su tipo; renombrar un campo del esquema propaga el cambio y subraya en rojo cada uso viejo; y un error de tecleo deja de ser un misterio de medianoche para convertirse en un aviso inmediato. La colección no solo blinda el build: acelera cada minuto que pasas escribiendo la plantilla.
Sumadas, estas ventajas dibujan un patrón: cada una convierte un acto de fe en una certeza verificada. Antes confiabas en que el campo existía, en que su tipo era el correcto, en que no lo habías escrito mal; ahora lo sabes, porque algo lo comprobó por ti. Ese reemplazo sistemático de la fe por la garantía es, resumido en una frase, lo que te ofrece una content collection.
Tipado
Cada entrada llega con el tipo exacto de tu esquema. El editor autocompleta y el compilador vigila cada acceso.
Validado
Ningún fichero que incumpla el esquema pasa el build. Los datos rotos no llegan a producción.
Con loader
El origen del contenido es explícito: ficheros locales hoy, una API remota mañana, sin cambiar el consumo.
Una sola API
Se consulta siempre igual, con astro:content, sin importar de dónde salgan los datos.
El contenido, tratado como una tabla
La mejor forma de interiorizar el cambio es dejar de pensar en “ficheros de Markdown” y empezar a pensar en “filas de una tabla”. La colección es la tabla; el esquema, sus columnas con su tipo; cada entrada, una fila que las respeta. Bajo esa luz, getCollection no lee una carpeta: consulta una tabla y devuelve sus filas ya tipadas, listas para filtrar y ordenar.
import { getCollection } from 'astro:content';
const posts = await getCollection('blog');
const recientes = posts
.filter((post) => !post.data.draft)
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf())
.slice(0, 5);
Descartar borradores, ordenar por fecha, quedarte con los cinco últimos: son operaciones de consulta sobre datos, no de manipulación de texto. Y son legítimas porque cada post.data tiene una forma garantizada —draft es un booleano, pubDate una fecha—, de modo que el filtro y el orden no pueden tropezar con un tipo inesperado. Ese es el terreno que preparan las collections: uno donde el contenido se interroga con la misma soltura y la misma seguridad que los datos de una aplicación, en lugar de leerse con lupa y cruzar los dedos.
El giro es tan mental como técnico. Un fichero de Markdown parece un documento; una entrada de colección es un registro. El primero se abre y se lee; el segundo se consulta y se combina. Nada en el disco cambia —siguen siendo .md con su frontmatter—, pero la lente con la que los miras determina lo que puedes hacer con ellos, y la lente de la colección es la que escala a cientos de páginas sin despeinarse.
Leer ficheros a mano es un enfoque schema-on-read: cada lector asume una forma y espera acertar. Las collections lo invierten a un schema-on-write: la forma se declara una vez, en el esquema, y se impone a cuanto entra. Es el mismo salto que dieron las bases de datos al pasar de guardar documentos informes a exigir un esquema en la escritura. Ganas coherencia garantizada a cambio de declarar por adelantado qué esperas encontrar.
El verdadero valor de las content collections no es el autocompletado ni la comodidad de una API única, sino un principio silencioso de la buena ingeniería: empujar los errores hacia la izquierda, hacia el momento más temprano y barato de detectarlos. Leer ficheros a mano no es solo incómodo, es una apuesta a que ningún dato estará jamás mal formado —una apuesta que el tiempo siempre acaba perdiendo—. Al declarar un esquema, conviertes una promesa implícita y frágil (“estos ficheros tienen la forma que espero”) en un contrato explícito que una máquina verifica antes de desplegar. Es la misma idea que sostiene los sistemas de tipos frente a los lenguajes dinámicos, las migraciones validadas frente al SQL a pelo, o los tests frente a la esperanza: cada uno mueve la detección del fallo desde la ejecución en producción hacia la compilación, donde arreglarlo cuesta un segundo y no una incidencia. Interiorizar esto reordena cómo piensas el contenido de un sitio. Deja de ser texto que lees con cuidado y pasa a ser datos con forma garantizada, tan fiables como una fila de una base de datos con su esquema. Y una vez que los datos son fiables por construcción, todo lo que edificas encima —consultas, referencias, imágenes— hereda esa fiabilidad sin que tengas que volver a comprobarla a mano. Ese es el regalo que hace una colección: no te ahorra escribir un bucle, te devuelve la confianza en que el contenido es lo que dice ser.
- Toma un puñado de Markdown con frontmatter y cárgalos con
import.meta.glob; comprueba queentry.frontmatteresanyy que un campo mal escrito no da ningún aviso. - Borra a mano un campo obligatorio de uno de los ficheros y observa que nada falla hasta que visitas esa página concreta.
- Enumera los cuatro riesgos de la lectura manual —sin tipos, sin validación, tipos accidentales, errores tardíos— y señala cuál te ha mordido alguna vez.
- Razona por qué mover la detección de un error de “en producción” a “en el build” no es una mejora menor, sino un cambio de naturaleza.