Content Collections
Content Collections tipa y valida Markdown/MDX/JSON con Zod en build — este mismo sitio está construido enteramente sobre esto (ver src/content.config.ts, la config real detrás de cada colección que ves en la sidebar).
Definir una colección
Sección titulada «Definir una colección»Vive en src/content.config.ts, en la raíz de src/ (no dentro de content/). loader dice de dónde salen las entradas; schema valida su frontmatter con Zod. Esto es la llamada Content Layer API, la forma actual desde Astro 5 — antes (Astro 2–4) una colección se detectaba sola por el nombre de la carpeta dentro de src/content/, sin declarar loader. Ese modelo implícito ya no existe: hoy toda colección se declara aquí, explícita, con su loader.
La ruta es exacta, no una convención flexible: Astro solo reconoce colecciones declaradas en src/content.config.ts. Moverlo a src/content/index.ts (o cualquier otra ruta, por prolijo que parezca) no rompe el build con un error claro — la colección simplemente queda vacía en silencio. El síntoma aparece más adelante, en astro check, como errores de tipos que no mencionan el archivo movido: Property 'data' does not exist on type 'never' o Argument of type "..." is not assignable to parameter of type 'never' en cualquier componente que consuma esa colección. Si ves ese error, lo primero a revisar es la ruta exacta de content.config.ts, no el componente donde aparece el error.
import { glob } from "astro/loaders"import { z } from "astro/zod"import { defineCollection } from "astro:content"
const blog = defineCollection({ loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }), schema: z.object({ title: z.string(), pubDate: z.coerce.date(), draft: z.boolean().default(false) })})
export const collections = { blog }Loaders: glob y file
Sección titulada «Loaders: glob y file»glob lee varios archivos (típicamente Markdown, uno por entrada). file lee un único JSON/YAML/TOML con varias entradas adentro — cada una necesita un id único.
glob({ pattern: "**/*.md", base: "./src/content/blog" })
file("src/data/autores.json") // requiere { id: "...", ... } por entradaLeer entradas
Sección titulada «Leer entradas»import { getCollection, getEntry } from "astro:content"
const posts = await getCollection("blog")const publicados = await getCollection("blog", ({ data }) => !data.draft)
const post = await getEntry("blog", "primer-post")Renderizar el contenido
Sección titulada «Renderizar el contenido»render(entry) compila el Markdown/MDX a un componente <Content />, más la lista de headings (para un índice de la página).
---import { render, getEntry } from "astro:content"
const post = await getEntry("blog", "primer-post")const { Content, headings } = await render(post)---
<Content />Referenciar otra colección
Sección titulada «Referenciar otra colección»reference() tipa un campo como id de otra colección, en vez de un string suelto — falla en build si apunta a algo que no existe.
import { reference } from "astro:content"
const blog = defineCollection({ loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }), schema: z.object({ title: z.string(), autor: reference("autores"), relacionados: z.array(reference("blog")).default([]) })})import { getEntries, getEntry } from "astro:content"
const post = await getEntry("blog", "primer-post")const autor = await getEntry(post.data.autor)const relacionados = await getEntries(post.data.relacionados)APIs de colección en una mirada
Sección titulada «APIs de colección en una mirada»| API | Uso |
|---|---|
defineCollection({ loader, schema }) | Declarar una colección |
glob() / file() | Loader desde varios archivos / uno solo |
getCollection(nombre, filtro?) | Todas las entradas, con filtro opcional |
getEntry(nombre, id) | Una entrada puntual |
render(entry) | { Content, headings } para renderizar el body |
reference(coleccion) | Tipar un campo como referencia a otra colección |
getEntries(refs) | Resolver varias referencias directamente |
Diseño del esquema y del contenido
Sección titulada «Diseño del esquema y del contenido»- El schema es la única fuente de verdad del frontmatter — si un campo no está declarado ahí, TypeScript no lo conoce aunque exista en el
.md. reference()valida que el id exista, pero recién en build — una referencia rota no rompe el editor, rompe elastro build.- Cambiar el esquema no actualiza los tipos generados automáticamente en el editor: ejecuta
astro sync—opnpm syncen este proyecto— después de modificarcontent.config.ts.