Contenido dinámico con Markdown y MDX
1. Escribir contenido con Markdown #
Markdown permite redactar títulos, listas, enlaces y párrafos en texto plano. El frontmatter entre --- contiene los metadatos; el cuerpo contiene el artículo.
📄 src/content/posts/primer-post.md
1--- 2title: "Mi primer post con Astro" 3date: "2026-10-29" 4author: "Ángel González" 5tags: ["astro", "markdown"] 6--- 7 8## Mi primera entrada 9 10Este artículo utiliza **Markdown** y una colección de Astro 7. 11 12- Escribo contenido. 13- Astro valida sus datos. 14- La plantilla lo presenta como HTML.
2. Organizar el blog #
1src/ 2├── content.config.ts 3├── content/posts/ 4│ ├── primer-post.md 5│ └── post-interactivo.mdx 6├── components/Counter.astro 7└── pages/blog/ 8 ├── index.astro 9 └── [...id].astro
En Astro 7 cada colección define un loader. La carpeta src/content/posts es una elección de organización: el loader también podría leer desde src/data/blog.
3. Configurar la colección #
📄 src/content.config.ts
1import { defineCollection } from 'astro:content'; 2import { glob } from 'astro/loaders'; 3import { z } from 'astro/zod'; 4 5const posts = defineCollection({ 6 loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/posts' }), 7 schema: z.object({ 8 title: z.string(), 9 date: z.coerce.date(), 10 author: z.string().optional(), 11 tags: z.array(z.string()).default([]), 12 }), 13}); 14 15export const collections = { posts };
glob() selecciona los archivos y genera su id. El esquema valida los metadatos y convierte date en un objeto Date. El archivo de configuración está directamente en src/; no uses el antiguo src/content/config.ts.
4. Mostrar el listado #
📄 src/pages/blog/index.astro
1--- 2import { getCollection } from 'astro:content'; 3 4const posts = (await getCollection('posts')).sort( 5 (a, b) => b.data.date.valueOf() - a.data.date.valueOf(), 6); 7--- 8<!doctype html> 9<html lang="es"> 10 <head> 11 <meta charset="UTF-8" /> 12 <meta name="viewport" content="width=device-width, initial-scale=1" /> 13 <title>Blog</title> 14 </head> 15 <body> 16 <h1>Blog</h1> 17 <ul> 18 {posts.map(post => ( 19 <li> 20 <a href={`/blog/${post.id}/`}>{post.data.title}</a> 21 <time datetime={post.data.date.toISOString()}> 22 {post.data.date.toLocaleDateString('es-ES')} 23 </time> 24 </li> 25 ))} 26 </ul> 27 </body> 28</html>
getCollection('posts') devuelve las entradas que proporciona el loader configurado. post.id identifica cada entrada y post.data contiene los metadatos validados. El orden no está garantizado: por eso lo establecemos explícitamente.
5. Crear las páginas de detalle #
📄 src/pages/blog/[...id].astro
1--- 2import type { GetStaticPaths } from 'astro'; 3import { getCollection, render, type CollectionEntry } from 'astro:content'; 4 5export const getStaticPaths = (async () => { 6 const posts = await getCollection('posts'); 7 return posts.map(post => ({ 8 params: { id: post.id }, 9 props: { post }, 10 })); 11}) satisfies GetStaticPaths; 12 13type Props = { post: CollectionEntry<'posts'> }; 14const { post } = Astro.props; 15const { Content } = await render(post); 16--- 17<!doctype html> 18<html lang="es"> 19 <head> 20 <meta charset="UTF-8" /> 21 <meta name="viewport" content="width=device-width, initial-scale=1" /> 22 <title>{post.data.title}</title> 23 </head> 24 <body> 25 <a href="/blog/">Volver al blog</a> 26 <article> 27 <h1>{post.data.title}</h1> 28 <p>{post.data.date.toLocaleDateString('es-ES')}</p> 29 <Content /> 30 </article> 31 </body> 32</html>
getStaticPaths() indica qué páginas generar durante el build. El parámetro [...id] permite también IDs con subcarpetas, como guias/primeros-pasos. render(post) devuelve el componente del cuerpo; no se invoca un método en la entrada.
6. Añadir MDX e interactividad #
Instala la integración antes de crear tu primer .mdx:
1npx astro add mdx
MDX permite importar componentes dentro del artículo. Si un componente de React necesita interactividad, también necesita una directiva como client:visible.
📄 src/content/posts/post-interactivo.mdx
1--- 2title: "Un artículo interactivo" 3date: "2026-10-29" 4--- 5 6import Counter from '../../components/Counter.astro'; 7 8## Prueba el contador 9 10Este componente Astro utiliza un script del navegador: 11 12<Counter start={5} /> 13<Counter start={10} />
📄 src/components/Counter.astro
1--- 2interface Props { start?: number } 3const { start = 0 } = Astro.props; 4--- 5<astro-counter data-start={start}> 6 <button type="button">Contador: {start}</button> 7</astro-counter> 8 9<script> 10 class AstroCounter extends HTMLElement { 11 connectedCallback() { 12 const button = this.querySelector('button'); 13 if (!button || button.dataset.ready) return; 14 button.dataset.ready = 'true'; 15 let count = Number(this.dataset.start ?? 0); 16 button.addEventListener('click', () => { 17 count += 1; 18 button.textContent = `Contador: ${count}`; 19 }); 20 } 21 } 22 if (!customElements.get('astro-counter')) { 23 customElements.define('astro-counter', AstroCounter); 24 } 25</script>
El frontmatter se ejecuta en el servidor; data-start transmite el valor al navegador. El custom element inicializa cada contador por separado sin IDs duplicados y funciona cuando se inserta tras una navegación con ClientRouter.
7. Etiquetas y autores #
Dentro de la plantilla de detalle puedes añadir:
1{post.data.author && <p>Autor: {post.data.author}</p>} 2<ul aria-label="Etiquetas"> 3 {post.data.tags.map(tag => <li>{tag}</li>)} 4</ul>
Para nuevas colecciones, añade su definición y loader al mismo src/content.config.ts. Los datos se incorporan durante el build: al publicar un nuevo archivo hay que volver a compilar y desplegar.
8. Comprueba tu blog #
Ejecuta npx astro check y npm run build. Si no tienes las herramientas de comprobación, instala npm install -D @astrojs/check typescript. Comprueba /blog/, el detalle de ambos formatos y que los dos contadores funcionan independientemente. Un error en un metadato obligatorio debe detectarse durante la compilación.
Documentación de colecciones · Scripts y datos del servidor.
Resumen del tema
- Configura colecciones en
src/content.config.tscon un loader y un esquema Zod. - Importa
zdesdeastro/zod. - Consulta entradas con
getCollection(); enlaza utilizando suid. - Genera rutas estáticas con
getStaticPaths()y el cuerpo conrender(post). - Instala MDX, importa sus componentes y configura sus interacciones de cliente.