Markdown en Astro: frontmatter, layout y slugs
El archivo .md como página de pleno derecho: cómo el file-based routing convierte un Markdown en una ruta, cómo el frontmatter aporta datos tipables, cómo la propiedad layout envuelve el documento en un componente, y cómo Astro genera de forma determinista el slug de la página y los id de cada encabezado.
Astro trata el Markdown como un ciudadano de primera: un .md en src/pages no es un dato que haya que cargar, sino una página que existe por el hecho de estar ahí. El mismo enrutado por ficheros que publica un .astro publica un .md, y el compilador se encarga del resto: parsea el frontmatter, transforma el cuerpo en HTML, lo envuelve en el layout que le indiques y le cuelga identificadores estables a cada encabezado. Entender esa cadena —de fichero a ruta, de cabecera a datos, de texto a documento maquetado— es la base sobre la que se apoya todo lo demás de este nivel.
- Comprender por qué un
.mdensrc/pagesse convierte en ruta sin registro alguno. - Distinguir el frontmatter como bloque de datos frente al cuerpo como contenido.
- Usar la propiedad
layoutpara envolver un Markdown en un componente de maquetación. - Predecir cómo Astro deriva el slug de la página y los
idde cada encabezado.
El archivo .md como página de pleno derecho
En Astro, el enrutado no es una tabla que mantienes: es una consecuencia de dónde pones los ficheros. Todo lo que vive bajo src/pages y sabe producir HTML se convierte en una ruta, y el Markdown sabe producir HTML. Un src/pages/acerca.md publica /acerca sin que declares nada, igual que lo haría un .astro. No hay un paso de importación ni un registro central; el fichero es la página.
Esa equivalencia tiene una consecuencia conceptual fuerte: el Markdown deja de ser un formato de datos que otro código consume y pasa a ser una unidad de publicación autónoma. El compilador de Astro lo reconoce como un módulo de página, extrae su frontmatter, compila su cuerpo a HTML y emite el fichero estático correspondiente. Lo que escribes en prosa llana termina siendo un documento navegable, y el único requisito es la ubicación.
La misma mecánica cubre dos extensiones. Un .md es Markdown puro; un .mdx añade la capacidad de importar y usar componentes dentro del contenido, entrelazando prosa y JSX. Para el enrutado y el layout no hay diferencia: ambos se autopublican desde src/pages y ambos aceptan la propiedad layout. La elección depende de si el documento necesita componentes e interactividad —entonces .mdx— o le basta con texto enriquecido —entonces .md—. Esta misma guía está escrita en .mdx, y por eso puede intercalar tarjetas y diagramas entre los párrafos sin salir del documento.
Que sea una unidad autónoma tiene una cara muy concreta en el build: cada Markdown de src/pages produce su propio fichero HTML estático, listo para servirse desde cualquier CDN sin un servidor detrás. Nadie interpreta el Markdown al vuelo cuando alguien pide la página; la interpretación ocurrió una vez, al compilar, y lo que queda es un .html inerte y rapidísimo. El Markdown es la fuente; el HTML estático, el producto.
---
title: "Sobre este sitio"
layout: ../layouts/Base.astro
---
# Sobre este sitio
Este párrafo se compila a HTML y se sirve en `/acerca`.
Conviene separar este caso —Markdown como página— del Markdown como contenido de una colección, que verás gobernado por getCollection. En src/pages el fichero se autopublica; en una colección el fichero es un dato que consultas y decides dónde mostrar. Son dos modos legítimos y complementarios: la página suelta para lo estático y singular, la colección para lo que se repite y se lista.
Markdown como página
Vive en src/pages, se autopublica como ruta por su mera ubicación y se maqueta con la propiedad layout. La opción natural para páginas singulares: un “acerca de”, unos términos legales, una portada estática.
Markdown como colección
Vive bajo src/content, no publica nada por sí mismo y se consulta con getCollection. La opción natural para lo que se repite y se lista: los posts de un blog, las fichas de un catálogo, las entradas de un changelog.
La regla para elegir es simple: si el documento es único y su URL debe reflejar dónde lo guardas, es una página; si pertenece a un conjunto homogéneo que vas a ordenar, filtrar o paginar, es una colección. Confundirlos no rompe nada, pero te hace nadar contra corriente: paginar páginas sueltas es incómodo, y publicar una colección exige escribir la ruta que la recorre. Cada modo brilla en su terreno.
Un .md solo se convierte en ruta si vive bajo src/pages. El mismo fichero en src/content o en cualquier otra carpeta no publica nada por sí mismo: es material que otro código debe importar o consultar. Es el tropiezo más común al empezar —escribir una página en src/markdown y no entender por qué la URL da un 404—. La ubicación no es una preferencia de orden, es la condición que activa el enrutado.
Frontmatter: datos en la cabecera
El frontmatter es el bloque delimitado por dos líneas de tres guiones al principio del fichero, escrito en YAML. No es contenido: es metadato. Ahí declaras el título, la fecha, las etiquetas o cualquier campo que tu maquetación necesite. Astro lo parsea y lo expone como un objeto, separando limpiamente los datos sobre el documento del cuerpo del documento.
Esa frontera importa porque son dos naturalezas distintas. El cuerpo se compila a HTML y se ve; el frontmatter se lee como estructura y alimenta decisiones —qué layout usar, qué título poner en la pestaña, cómo ordenar una lista—. Mezclarlos sería como confundir las columnas de una tabla con el texto de una celda: relacionados, pero de planos diferentes.
En una página suelta, ese frontmatter es libre: escribes los campos que tu layout necesite y nadie los valida. Es a la vez su comodidad y su fragilidad, porque un title mal escrito no se detecta hasta que la plantilla lo lee vacío. Es justo la carencia que las colecciones resuelven con un esquema de Zod, capaz de rechazar en el build un frontmatter que no cumpla el contrato. Para lo singular, la libertad del frontmatter suelto compensa; para lo que se repite cien veces, la red del esquema evita cien errores callados.
El frontmatter se parsea como YAML estricto: la indentación cuenta, los dos puntos separan clave y valor, y una fecha sin comillas se interpreta como fecha real, no como texto. Un error de sangría no rompe tu prosa, pero sí impide que Astro lea el bloque. Cuando un campo no llega a la plantilla, el primer sospechoso es casi siempre una comilla o un espacio de más en la cabecera.
El layout mágico de las páginas Markdown
Una página Markdown, por sí sola, solo produce el HTML de su cuerpo: unos encabezados, unos párrafos, sin <html>, sin <head>, sin cabecera ni pie. La propiedad especial layout del frontmatter cierra ese hueco. Apúntala a un componente .astro y Astro envolverá automáticamente el HTML compilado dentro de ese componente, entregándotelo por su <slot />.
Lo que hace “mágico” a este mecanismo es cuánto te da sin pedir nada a cambio. El componente de layout recibe props que Astro rellena solo: frontmatter, con todos los campos de la cabecera ya parseados; url, con la ruta pública de la página; file, con su ruta en disco; y headings, con la lista de encabezados del documento. El cuerpo compilado llega por el <slot />.
---
// src/layouts/Base.astro
const { frontmatter, headings } = Astro.props;
---
<html lang="es">
<head><title>{frontmatter.title}</title></head>
<body>
<h1>{frontmatter.title}</h1>
<nav>{headings.length} secciones</nav>
<slot />
</body>
</html>
El reparto es nítido y merece grabarse: el Markdown aporta el contenido y los datos; el layout aporta el armazón del documento y decide cómo presentarlos. El autor escribe prosa y una cabecera mínima; la maquetación vive centralizada en un componente que cien páginas comparten. Cambiar el título de la pestaña o añadir una etiqueta al <head> es tocar un fichero, no cien.
La ruta que pones en layout se resuelve como cualquier importación: relativa al propio Markdown, con ../ para subir de carpeta hasta src/layouts. Y es opcional: un Markdown sin layout se sirve igual, solo que como el HTML desnudo de su cuerpo, sin <html> ni <head>. Esa opcionalidad es coherente con la filosofía del framework —nada es obligatorio hasta que lo necesitas—, pero en la práctica casi toda página quiere su marco, y declarar el layout es el primer gesto tras crear el fichero.
Un layout invocado desde el frontmatter de un .md recibe siempre el mismo juego de props, listos en Astro.props: frontmatter con la cabecera ya parseada, url con la ruta pública, file con la ruta en disco, headings con la lista de encabezados, y las funciones rawContent y compiledContent para el texto crudo y el HTML compilado. Conocer este inventario te ahorra adivinar: cualquier dato del documento que el layout necesite, o vive en frontmatter, o es una de estas propiedades hermanas que Astro rellena por ti.
Slugs e id de encabezado
Dos identificadores se generan de forma determinista y conviene saber de dónde salen. El primero es el slug de la página: en src/pages proviene de la ruta del fichero. src/pages/blog/guia.md publica /blog/guia; el nombre del fichero, sin la extensión, es el último segmento de la URL. No hay aleatoriedad ni configuración: la estructura de carpetas es la estructura de rutas.
src/pages/acerca.md -> /acerca
src/pages/blog/guia.md -> /blog/guia
src/pages/blog/index.md -> /blog
src/pages/docs/api/v2.md -> /docs/api/v2
Un caso especial afina la regla: un fichero llamado index.md no aporta su nombre a la URL, sino que representa la raíz de su carpeta. src/pages/blog/index.md publica /blog, no /blog/index. Así, las carpetas construyen la jerarquía de rutas y el index marca su punto de entrada, exactamente como haría un .astro. El slug no es más que la traducción literal del árbol de ficheros al árbol de URL.
El segundo son los id de los encabezados. Al compilar el Markdown, Astro recorre cada #, ##, ### y le asigna un id derivado de su texto mediante github-slugger: pasa a minúsculas, sustituye espacios por guiones y descarta la puntuación. Así, ## Mi Sección se convierte en <h2 id="mi-seccion">, y esa ancla existe sin que muevas un dedo, lista para enlazarse desde una tabla de contenidos o desde fuera con un #mi-seccion.
## Mi Sección
### Detalles Finos
se traduce, en el HTML generado, a encabezados con ancla propia:
<h2 id="mi-seccion">Mi Sección</h2>
<h3 id="detalles-finos">Detalles Finos</h3>
flowchart LR MD[archivo punto md] --> FM[frontmatter parseado] MD --> BODY[cuerpo compilado a html] FM --> LAY[layout via propiedad layout] BODY --> IDS[ids de encabezado via slugger] BODY --> LAY LAY --> PAGE[pagina final servida] style MD fill:#89b4fa,color:#11111b style PAGE fill:#a6e3a1,color:#11111b
Que ambos identificadores sean deterministas es una garantía, no un detalle. Significa que puedes enlazar a /blog/guia#mi-seccion con la certeza de que la ancla existirá mientras el encabezado exista, sin inventar id a mano ni temer colisiones arbitrarias. Cuando dos encabezados comparten texto, github-slugger desambigua añadiendo un sufijo numérico, de modo que la unicidad se mantiene incluso en documentos repetitivos.
Esa doble determinación —slug de página y slug de encabezado— es el cimiento del deep linking: enlazar no ya a una página, sino a un punto exacto dentro de ella. Un índice lateral que salta a cada sección, un enlace compartido que abre justo donde importa, una cita anclada desde otro artículo: todo se sostiene sobre identificadores que existen y no cambian por capricho. Lo que aquí parece un detalle de implementación es la base sobre la que la próxima lección levantará tablas de contenido enteras a partir de la lista de encabezados que Astro ya calculó.
Lo que Astro hace con un Markdown de páginas es elevar una convención humilde —dónde guardas el fichero— a la categoría de contrato del sistema. En muchos frameworks, publicar una página exige tres actos separados: crear el contenido, registrar una ruta y conectar una plantilla. Astro colapsa los tres en uno: pones el fichero en su sitio y el resto se deduce. La ruta se deriva de la ubicación, la maquetación se declara con una línea de frontmatter, y los identificadores internos se generan del propio texto. Esa deducción no es comodidad superficial; es una decisión de diseño que elimina categorías enteras de error. No hay una tabla de rutas que pueda desincronizarse del disco, porque el disco es la tabla. No hay id inventados que puedan colisionar, porque se derivan del contenido con una regla estable. No hay una plantilla olvidada, porque su ausencia es visible en la cabecera. Cuando interiorizas que en Astro la estructura de ficheros no describe el sitio sino que lo constituye, dejas de mantener configuración y empiezas a mantener contenido; y esa es exactamente la clase de trabajo que quieres que sobreviva a mil páginas.
- Crea
src/pages/notas/primera.mdcon un frontmatter que incluyatitley una propiedadlayout, y predice la URL pública antes de arrancar el servidor. - Escribe un
src/layouts/Nota.astroque leafrontmatter.titledeAstro.props, lo pinte en el<title>y coloque el cuerpo con un<slot />. - Añade tres encabezados de distinto nivel al Markdown y comprueba en el HTML generado qué
idrecibe cada uno; justifica la transformación aplicada al texto. - Enlaza desde otra página a una sección concreta usando el
idderivado y razona por qué esa ancla es fiable sin haberla escrito a mano.