Consultar y renderizar: getCollection, getEntry y render
Convertir una colección tipada en páginas: cargar entradas con getCollection y getEntry, filtrarlas y ordenarlas con datos ya seguros, renderizar su cuerpo con render y el componente Content, y derivar rutas del contenido con getStaticPaths.
getCollection y getEntry
Dos funciones de astro:content cubren la consulta. getCollection devuelve todas las entradas de una colección como un array; getEntry devuelve una sola, identificada por su id. Ambas son asíncronas, así que se esperan con await en el frontmatter del componente.
Que sean asíncronas no implica que consulten nada en tiempo de ejecución: en un sitio estático, estas llamadas se resuelven durante el build contra el almacén ya cargado, y su coste desaparece del navegador. El await está ahí porque la API es uniforme —la misma que usarías si el loader trajera datos remotos—, no porque haya una espera real en producción. Escribes código asíncrono y entregas HTML estático.
import { getCollection, getEntry } from 'astro:content';const posts = await getCollection('blog');const uno = await getEntry('blog', 'mi-post');Cada entrada es un objeto con una forma fija: un id estable, el data ya validado y tipado según tu esquema, el body con el texto crudo del Markdown y el nombre de la collection. Como data conoce su tipo, post.data.title es string y post.data.pubDate es Date, con autocompletado y con el compilador vigilando cada acceso. La consulta no devuelve texto opaco, sino registros con contrato.
getEntry merece un cuidado especial: si le pasas un id que no existe, devuelve undefined, no lanza una excepción. Esa elección deja en tus manos decidir qué hacer con lo ausente —redirigir, mostrar un 404, usar un valor de reserva—, pero te obliga a comprobarlo. En una ruta que recibe el id desde la URL, ese chequeo es la diferencia entre una página de error clara y un fallo turbio al leer data de algo que no está.
Filtrar y ordenar
Al ser un array de datos tipados, una colección se manipula con las herramientas de siempre de JavaScript. getCollection acepta además un segundo argumento, una función de filtro que se aplica durante la carga: útil para descartar borradores sin traerlos siquiera al array.
const publicados = await getCollection('blog', ({ data }) => !data.draft);const ordenados = publicados.sort( (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),);Ordenar por fecha descendente, agrupar por etiqueta, quedarte con los tres más recientes: todo es código normal sobre un array, con la ventaja de que el tipado impide errores tontos —restar dos fechas está permitido, restar dos títulos no compila—.
Ese detalle —que el tipo te impida una operación absurda— es el tipado ganándose el sueldo. En una lectura a mano, ordenar por una fecha que en realidad es una cadena produce un orden alfabético traicionero que parece funcionar hasta que llega diciembre y “10” se coloca antes que “9”. Con la colección, pubDate es un Date, restarlo es legítimo y el orden sale correcto por construcción; el error ni siquiera es posible. La colección se comporta como una tabla en memoria que consultas con el lenguaje que ya sabes.
Conviene decidir dónde filtras. La función que pasas a getCollection se evalúa durante la carga y es ideal para exclusiones amplias —descartar borradores— porque esas entradas ni siquiera llegan al array. El filter, el sort o el slice posteriores operan sobre lo ya cargado y son el lugar natural para las vistas concretas: los cinco últimos, los de una etiqueta, los de un año. Una criba general en la carga; los recortes de cada página, después.
---import { getCollection } from 'astro:content';const posts = (await getCollection('blog', ({ data }) => !data.draft)).sort( (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),);---<ul> {posts.map((post) => ( <li><a href={`/blog/${post.id}`}>{post.data.title}</a></li> ))}</ul>render: del cuerpo al HTML
El data te da los metadatos, pero el cuerpo del Markdown —los párrafos, los encabezados, el código— hay que compilarlo a HTML para mostrarlo. Esa es la tarea de render, una función que importas de astro:content, a la que pasas la entrada y que te devuelve un componente Content listo para colocar en la plantilla.
---import { getEntry, render } from 'astro:content';const post = await getEntry('blog', 'mi-post');const { Content, headings } = await render(post);---<article> <h1>{post.data.title}</h1> <Content /></article>render separa deliberadamente el dato de su representación: mientras no lo llamas, la entrada es puro dato consultable; cuando lo llamas, obtienes su HTML. Junto al Content recibes headings, la lista de encabezados del documento, con la que se construye una tabla de contenidos sin analizar el texto a mano.
El Content que devuelve render es un componente de Astro como cualquier otro: lo colocas en la plantilla, hereda los estilos de su contexto y respeta los componentes que hayas mapeado para el Markdown. Por debajo, render compila el body crudo de la entrada al HTML final, un trabajo que solo se hace cuando lo pides. Por eso una página índice, que lista títulos y fechas pero no cuerpos, no paga el coste de renderizar cien documentos que nadie va a leer en esa vista.
De la colección a las rutas
El patrón que lo une todo es generar una página por entrada. En una ruta dinámica como src/pages/blog/[slug].astro, la función getStaticPaths recorre la colección y produce un camino por cada post, pasando la entrada entera como prop.
---import { getCollection, render } from 'astro:content';export async function getStaticPaths() { const posts = await getCollection('blog'); return posts.map((post) => ({ params: { slug: post.id }, props: { post }, }));}const { post } = Astro.props;const { Content } = await render(post);---<h1>{post.data.title}</h1><Content />Cada id se convierte en un segmento de URL, y cada entrada, en una página completa. La misma colección que aquí genera las páginas de detalle puede, en otra ruta, alimentar el índice del blog, y en un endpoint, un feed RSS. Una fuente, muchas vistas: el contenido se consulta una vez y se presenta de tantas formas como haga falta.
Ese patrón —getStaticPaths que recorre una colección y emite una ruta por entrada— es el puente entre las dos mitades del framework: el contenido tipado de este nivel y el enrutado por ficheros que ya conoces. Los datos deciden cuántas páginas existen; el fichero [slug].astro decide cómo es cada una. Añadir un post es crear un Markdown, y el sitio crece solo, sin tocar rutas ni registros.
El mismo patrón se estira sin romperse. Para paginar, getStaticPaths puede emitir una ruta por página de resultados en vez de por entrada; para un sitio en servidor, la consulta ocurre por petición en lugar de en el build, con el mismo código. Cambia cuándo y cuántas veces se ejecuta la consulta, no cómo se escribe. Aprendido una vez, el gesto de pedir a la colección y decidir la presentación se reutiliza en cada modo de renderizado.