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.
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 seguridadconst files = import.meta.glob('./posts/*.md', { eager: true });const posts = Object.values(files);// posts[0].frontmatter es any: nadie valida title, pubDate ni tagsEl 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 DateAstro 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.
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.
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.