Layouts y frontmatter en MDX
Envolver una página MDX autónoma con un layout: la clave layout del frontmatter, la prop frontmatter que el layout recibe con los metadatos del documento, las props que Astro inyecta y las que no comparte con Markdown, y la alternativa de envolver a mano importando el layout como componente.
Una página MDX autónoma en src/pages produce solo el cuerpo del documento; le falta el <html>, el <head> y el marco compartido del sitio. El layout es quien pone ese marco, y MDX ofrece dos maneras de invocarlo: la declarativa —una clave layout en el frontmatter que Astro conecta por ti— y la explícita —importar el layout como un componente y envolver el contenido a mano—. Ambas terminan en lo mismo: un documento de contenido puro convertido en una página HTML completa.
- Asignar un layout a una página MDX autónoma con la clave
layoutde su frontmatter. - Leer los metadatos del documento dentro del layout a través de
Astro.props.frontmatter. - Conocer las props que Astro inyecta a un layout de MDX y las que no comparte con Markdown.
- Envolver un documento importando el layout como componente cuando necesitas más control.
La clave layout en una página MDX
Una página .mdx situada directamente en src/pages no puede rodear su contenido de un <html> sin repetirlo en cada fichero, así que Astro le ofrece un atajo declarativo: la clave layout del frontmatter, apuntando a la ruta del componente que hará de marco. Con esa sola línea, Astro compila el cuerpo del documento y lo inyecta en el <slot /> del layout indicado.
---
layout: ../../layouts/Articulo.astro
title: Guía de despliegue
fecha: 2026-07-20
---
# Cómo desplegar
El cuerpo entero de este documento se compila a HTML y aterriza
en el slot del layout, sin escribir una etiqueta de componente.
El mecanismo es idéntico al de una página Markdown: no importas nada ni invocas al layout con JSX; solo lo nombras. Astro hace dos cosas por ti al procesar la página. Primero, renderiza el cuerpo MDX a HTML y lo coloca en el hueco del layout. Segundo, pasa todo el frontmatter al layout para que pueda usar el título, la fecha o cualquier campo que hayas declarado. Una página de contenido obtiene así su marco completo con una línea de configuración.
Un detalle práctico ahorra un desconcierto habitual: la ruta de la clave layout es relativa al fichero .mdx que la escribe, no a la raíz del proyecto. Desde una página en src/pages/blog/, el layout de src/layouts/ se alcanza subiendo dos niveles con ../../layouts/, y equivocar la cuenta de puntos es la causa más común de que Astro no encuentre el marco. Si prefieres evitar el conteo, un alias de ruta configurado en el proyecto vuelve la referencia estable desde cualquier profundidad de carpeta.
La prop frontmatter dentro del layout
Dentro del layout, los metadatos del documento no llegan como props sueltas, sino agrupados bajo una única prop llamada frontmatter. Ese objeto contiene todos los campos que declaraste en la cabecera del .mdx, y desde él el layout lee lo que necesita para armar el documento.
---
// src/layouts/Articulo.astro
const { frontmatter } = Astro.props;
---
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8" />
<title>{frontmatter.title}</title>
</head>
<body>
<article>
<h1>{frontmatter.title}</h1>
<time>{frontmatter.fecha}</time>
<slot />
</article>
</body>
</html>
La razón de que los datos lleguen empaquetados y no dispersos es que su contenido es abierto: Astro no sabe qué campos declararás en tu frontmatter, así que los reúne todos bajo un mismo espacio de nombres en lugar de esparcirlos como props anónimas. El layout accede a cada uno con frontmatter.loquesea, y el <slot /> recibe el cuerpo ya compilado. Datos arriba, contenido en el hueco: el documento queda vestido.
Ese carácter abierto tiene un reverso: frontmatter es, por defecto, un objeto sin garantías, y un campo mal escrito o ausente no se detecta hasta que revienta al renderizar. Un layout robusto se defiende tipando lo que espera con una interface Props que declare la forma de frontmatter y dando valores por defecto al desestructurar. Así, el editor autocompleta los campos, el compilador avisa si el layout pide uno que el documento no trae, y el marco deja de confiar ciegamente en la cabecera del .mdx. Es el mismo salto de la fe a la garantía que ganan las content collections al validar con un esquema, aplicado aquí a mano en la frontera del layout.
Props inyectadas: qué llega y qué no
Junto a frontmatter, Astro entrega al layout de una página MDX un conjunto de props que no tuviste que declarar, útiles para construir cabeceras, índices y enlaces canónicos. Conviene conocerlas, y conviene sobre todo conocer una asimetría con Markdown que sorprende a quien la ignora.
frontmatter— el objeto con todos los campos de la cabecera del documento.file— la ruta absoluta del fichero de origen en tu disco.url— la URL pública de la página dentro del sitio.headings— la lista de encabezados del documento, con la que se genera una tabla de contenidos.
De todas ellas, headings es la que más juego da y la que más justifica leer las props inyectadas. Es un array de objetos con la profundidad, el texto y el slug de cada encabezado del documento, extraído por Astro sin que tú analices el texto. Con él, el layout construye un índice navegable —un <nav> con un enlace por sección— que se mantiene solo: si el autor añade un encabezado en el .mdx, aparece en el índice sin tocar el layout. Es un ejemplo perfecto de dato derivado que el marco calcula una vez y muchas piezas consumen.
La asimetría es esta: rawContent() y compiledContent(), las funciones que en un layout de Markdown devuelven el texto crudo y el HTML compilado, no existen en MDX. La razón es de fondo: un .md es una cadena de texto que se puede devolver tal cual, pero un .mdx es un módulo con imports y componentes que no se reduce a una cadena, así que esas funciones no tendrían un valor coherente que devolver. Si escribes un layout pensando servir a los dos formatos, no dependas de ellas.
Un layout que sirva por igual a .md y a .mdx debe evitar rawContent() y compiledContent(), porque en las páginas MDX serán undefined y romperán en tiempo de ejecución. Si de verdad necesitas el HTML compilado de una entrada, el camino robusto es renderizarla con la API de contenido y su componente Content, no arrancar la cadena del layout. Diseña el marco para lo que ambos formatos comparten, no para lo que solo uno ofrece.
flowchart TD MDX[pagina mdx con clave layout] --> AS[Astro conecta el layout] AS --> SLOT[cuerpo compilado al slot] AS --> FM[frontmatter inyectado como prop] AS --> EXTRA[file url y headings] FM --> LAY[Layout lee Astro.props] SLOT --> LAY EXTRA --> LAY LAY --> DOC[documento HTML completo] style LAY fill:#89b4fa,color:#11111b style DOC fill:#a6e3a1,color:#11111b
Envolver a mano importando el layout
La clave layout es cómoda pero limitada: solo pasa el frontmatter, y no te deja calcular props ni elegir a qué hueco va cada cosa. Cuando necesitas ese control, MDX te ofrece la vía explícita, que aprovecha que un documento MDX admite JSX: importas el layout como un componente y envuelves el contenido entre sus etiquetas, pasándole las props que quieras.
---
title: Guía avanzada
---
import Articulo from '../../layouts/Articulo.astro';
export const lecturaMin = 8;
<Articulo titulo={title} minutos={lecturaMin}>
## Contenido
El cuerpo va como hijo del layout, en su slot por defecto.
</Articulo>
Aquí no usas la clave layout: la omites, porque el marco lo pones tú con la etiqueta <Articulo>. A cambio de escribir un poco más, ganas todo lo que la vía declarativa te negaba: pasar props computadas como minutos, combinar varios valores, o incluso envolver solo una parte del documento. Recuerda el detalle de la lección de componentes —las líneas en blanco alrededor del contenido anidado— para que el Markdown interior se procese como tal.
La clave layout del frontmatter solo funciona para páginas .mdx que viven sueltas en src/pages. El contenido gestionado con content collections no la usa: allí el layout no se asigna en cada fichero, sino en la ruta dinámica que renderiza la colección, donde recuperas la entrada, obtienes su cuerpo con render y lo colocas dentro del layout como cualquier componente. No mezcles ambos mundos: el atajo layout es la vía cómoda para el MDX independiente; las colecciones tienen su propio patrón, más potente y tipado, que verás en su nivel.
Las dos vías de esta lección parecen opuestas —una es configuración declarativa, la otra código explícito— pero convergen en una verdad que es el corazón del asunto: a un layout le da exactamente igual de dónde salieron los datos que recibe. Un layout es una función pura de sus props a un documento HTML; le entran valores por Astro.props y le sale un <html> completo, y nada en su cuerpo pregunta cómo se produjeron esos valores. Da lo mismo que Astro los haya extraído del frontmatter de un .mdx y entregado bajo frontmatter, que se los hayas pasado tú a mano como props al envolver con JSX, o que vengan de una página .astro que los calculó desde una base de datos: el layout hace lo mismo en los tres casos, porque solo ve props, no procedencias. Esta indiferencia respecto al origen no es un detalle de implementación, sino una de las propiedades más valiosas que puede tener una pieza de software. Es lo que permite que un único Articulo.astro vista un texto escrito en MDX por un redactor, una página generada por código y una entrada de una colección remota, sin una sola rama que distinga el caso. Cada vez que diseñas un componente para que dependa solo de sus entradas y no de cómo se generaron, lo vuelves reutilizable en contextos que ni habías imaginado y trivial de probar, porque testearlo es darle props y mirar su salida. La lección práctica se sigue de aquí con nitidez: la clave layout del frontmatter y el envoltorio explícito con JSX no son dos maneras distintas de usar layouts, sino dos maneras de rellenar el mismo hueco de datos de una función que no interroga su origen. Elegir una u otra es una cuestión de conveniencia local —cuánto control necesitas en esa página concreta— y nunca una decisión que el layout deba conocer. Diseña tus marcos como funciones de sus datos, mantenlos ciegos a la procedencia, y descubrirás que el mismo puñado de layouts basta para todo tu contenido, venga de donde venga.
- Crea
Articulo.astroque leaAstro.props.frontmattery coloque título y fecha en el documento con un<slot />para el cuerpo; asígnalo a un.mdxcon la clavelayout. - Dentro del layout, imprime
headingsyurlpara ver los datos que Astro inyecta sin declararlos, y esboza cómo construirías un índice conheadings. - Comprueba que
rawContent()esundefineden la página MDX y explica por qué un módulo con imports no se reduce a una cadena de texto. - Reescribe la misma página usando la vía explícita: importa el layout, envuélvelo con JSX y pásale una prop computada con
export constque la clavelayoutno permitiría.