Componentes dentro de MDX
Cómo importar un componente .astro o de framework en la cabecera de un fichero MDX y usarlo como una etiqueta más entre la prosa: paso de props, contenido anidado, la diferencia entre componentes de Astro que se resuelven en el build y las islas de framework que exigen una directiva client, y el detalle de las líneas en blanco que decide si el Markdown anidado se procesa.
La razón de ser de MDX cabe en una frase: poder escribir un componente en medio de un párrafo. Importas la pieza en la cabecera del fichero, la colocas como una etiqueta entre tu texto, le pasas props y anidas contenido dentro, exactamente como en una plantilla .astro. Lo que cambia es el contexto: aquí el componente no vive rodeado de más código, sino de prosa, y esa mezcla es la que convierte un documento en algo componible.
- Importar un componente
.astroo de framework en la cabecera de un fichero MDX. - Intercalar componentes con la prosa y pasarles props y contenido anidado.
- Distinguir un componente de Astro, que se resuelve en el build, de una isla de framework que exige una directiva
client. - Dominar el detalle de las líneas en blanco que decide si el Markdown anidado se procesa como tal.
Importar un componente en MDX
Un fichero MDX empieza, como cualquier Markdown, con su frontmatter YAML entre guiones. Justo después vienen los import, que en MDX se escriben como en cualquier módulo de JavaScript: en la parte superior del cuerpo, antes de usar lo que traen. No hay una valla como en Astro; los imports conviven con la prosa, aunque por convención se agrupan arriba para que se vean de un vistazo.
---
title: Guía de inicio
---
import Aviso from '../components/Aviso.astro';
import Tarjeta from '../components/Tarjeta.astro';
# Primeros pasos
<Aviso tipo="tip">Instala las dependencias antes de seguir.</Aviso>
Un párrafo normal de Markdown entre dos componentes.
<Tarjeta titulo="Requisitos" />
La mecánica es idéntica a la composición en Astro: importas con una ruta relativa, el nombre debe empezar por mayúscula para que MDX lo trate como componente y no como etiqueta HTML, y a partir de ahí lo usas cuantas veces quieras. La diferencia es puramente ambiental. En un .astro, el componente aparece dentro de una plantilla; en un .mdx, aparece dentro de un documento, rodeado de encabezados y párrafos que son Markdown de verdad.
Los import de MDX son ESM de pleno derecho, con lo que ello implica. Se resuelven en el ámbito del módulo, no dentro de la prosa, así que van arriba y no pueden condicionarse ni anidarse en el texto. Admiten los alias de ruta que hayas configurado —un ~/components en lugar de una maraña de puntos y barras— y participan del grafo de dependencias del proyecto: si el componente importado cambia, tu documento se recompila con él. La mayúscula inicial no es decoración, sino la señal semántica que MDX lee para decidir si una etiqueta es tu componente o un elemento nativo, exactamente como en Astro.
Intercalar componentes con el texto
Un componente en MDX puede colocarse a nivel de bloque —solo, entre dos párrafos, como una pieza autónoma— o en línea, dentro de una frase, si su naturaleza lo permite. Recibe props con la sintaxis de JSX: texto entre comillas, y cualquier otro valor entre llaves. Y admite contenido anidado entre su etiqueta de apertura y la de cierre, que llega al componente por su slot por defecto.
import Panel from '../components/Panel.astro';
<Panel titulo="Resumen" destacado={true}>
Este texto anidado aterriza en el slot del panel.
</Panel>
El paso de props funciona como en Astro sin ninguna salvedad: titulo="Resumen" pasa una cadena, destacado={true} pasa un booleano, y una variable definida arriba se pasaría con total={total}. El componente los recibe por Astro.props si es .astro, o por sus props si es de framework. Esa continuidad es intencionada: MDX no inventa un modelo de componentes propio, reutiliza el que ya conoces y lo deja escribir en medio de la prosa.
La posición del componente importa tanto como sus props. Un componente colocado solo entre dos líneas en blanco es un bloque: ocupa su propia franja del documento, como un párrafo. Un componente escrito dentro de una frase es en línea: fluye con el texto que lo rodea. La distinción no la impone MDX, sino la naturaleza del componente y dónde lo pongas, igual que un <span> y un <div> conviven en HTML. Y cuando el componente declara varios huecos, diriges contenido a cada uno con el atributo slot="nombre" sobre un elemento anidado, exactamente como harías en una plantilla .astro.
<Panel titulo="Ventas">
El contenido sin marca cae en el slot por defecto del panel.
<span slot="pie">Actualizado hoy</span>
</Panel>
El span con slot="pie" se dirige al hueco con ese nombre; el resto, al slot por defecto. Las líneas en blanco alrededor del bloque interior no son opcionales: son las que le dicen a MDX que ahí dentro vuelve a haber Markdown, un punto al que la última sección de esta lección dedica toda su atención.
Todo lo que escribes entre la etiqueta de apertura y la de cierre de un componente en MDX se convierte en su slot por defecto, igual que en una plantilla .astro. Si el componente declara slots con nombre, diriges contenido a ellos con el atributo slot="nombre" sobre un elemento anidado. El componente no distingue si su contenido vino de una plantilla o de un documento MDX: solo ve marcado llegando a sus huecos.
Componentes de Astro frente a islas de framework
Aquí aparece la distinción que más confusiones evita. Un componente .astro importado en MDX se resuelve por completo durante el build: se expande a su HTML y desaparece como abstracción, sin enviar un solo byte de JavaScript al navegador. Un componente de framework —React, Vue, Svelte, Solid— es distinto: si lo colocas sin más, se renderiza a HTML estático en el build, pero permanece inerte en el cliente. Para que cobre vida —para que su estado y sus eventos funcionen— hay que hidratarlo con una directiva client.
import Estatico from '../components/Estatico.astro';
import Contador from '../components/Contador.jsx';
<Estatico />
<Contador client:visible />
La directiva client:visible convierte a Contador en una isla: una porción interactiva que Astro hidrata cuando entra en la pantalla, dejando el resto del documento como HTML puro. Las variantes —client:load, client:idle, client:visible, client:only— gradúan cuándo ocurre esa hidratación. La regla mental es nítida: un componente de framework sin directiva client es una foto estática; con directiva, es una isla viva. Un componente .astro nunca necesita directiva porque nunca aspira a vivir en el cliente.
Cada variante compra un compromiso distinto entre inmediatez y coste. client:load hidrata en cuanto carga la página, y se reserva para lo que debe reaccionar sin demora. client:idle espera a que el hilo principal quede libre, ideal para interactividad secundaria que no urge. client:visible retrasa la hidratación hasta que la isla entra en el viewport, perfecto para piezas del pie de una página larga que muchos visitantes nunca verán. client:only va más allá: omite el renderizado en el build y pinta el componente solo en el cliente, para casos que dependen de APIs del navegador sin equivalente en el servidor. Elegir la directiva es dosificar cuándo el usuario paga el JavaScript de esa isla, y en un documento la elección se toma isla por isla.
flowchart TD MDX[documento mdx] --> IMP[import del componente] IMP --> USO[uso como etiqueta JSX] USO --> A[componente astro] USO --> F[componente de framework] A --> HTML[HTML en el build sin JS] F --> DIR[con directiva client] DIR --> ISLA[isla hidratada en el cliente] style A fill:#a6e3a1,color:#11111b style ISLA fill:#f9e2af,color:#11111b
Ante la duda, un componente en un documento debería ser .astro estático. Cada isla que añades es JavaScript que el visitante descarga, analiza y ejecuta, y en un documento de lectura la mayoría de las piezas no necesitan estado ni eventos: un aviso, una tarjeta, una cita son HTML y nada más. Reserva la hidratación para lo que de verdad reacciona al usuario —un contador, un buscador, un formulario— y, cuando la uses, escoge la directiva más perezosa que el caso permita. Un documento con diez componentes y una sola isla se descarga casi tan rápido como uno sin ninguna; ese es el punto de las islas.
El detalle que rompe: las líneas en blanco
Existe una trampa sutil de MDX que conviene grabar antes de tropezar con ella. El Markdown que anidas dentro de un componente solo se procesa como Markdown si lo separas con líneas en blanco de las etiquetas del componente. Pegado a la etiqueta, el contenido se trata como texto o JSX crudo, y tus almohadillas y asteriscos aparecen literales en lugar de convertirse en títulos y negritas.
<!-- El markdown NO se procesa: pegado a las etiquetas -->
<Panel>
## Este título sale literal
</Panel>
<!-- El markdown SÍ se procesa: separado por líneas en blanco -->
<Panel>
## Este título se convierte en un h2
</Panel>
La causa es coherente con la naturaleza de MDX. Dentro de una etiqueta JSX, el compilador asume por defecto que está leyendo hijos de JSX, no Markdown; las líneas en blanco son la señal explícita de que ahí dentro vuelve a haber un bloque Markdown que debe parsearse. No es un capricho: es la frontera entre los dos lenguajes, marcada con el mismo separador que Markdown usa siempre para delimitar párrafos.
Poder incrustar un componente en medio de un párrafo parece una comodidad menor, pero cambia la categoría de lo que estás escribiendo. Un Markdown clásico es un texto: una secuencia lineal de bloques que se leen de arriba abajo y cuyo único poder expresivo es el formato. Un MDX con componentes es una interfaz: un árbol de piezas componibles del que la prosa es solo una hoja más, al mismo nivel que un gráfico interactivo, una alerta o una isla que reacciona al usuario. Esa promoción del documento a interfaz reordena quién puede hacer qué. El ingeniero ya no entrega plantillas cerradas que el redactor rellena con texto plano; entrega un vocabulario de componentes —una biblioteca de piezas con nombre y contrato— que el redactor combina libremente dentro de su prosa, sin tocar una línea de código de presentación. El contenido se vuelve, literalmente, programable por quien lo escribe, y la separación entre autor y desarrollador deja de ser un muro para convertirse en una interfaz negociada: los componentes son el contrato entre ambos. Hay aquí una lección de diseño que trasciende MDX. La composición es la operación más poderosa de la ingeniería porque escala sin límite: dos piezas que se componen bien dan lugar a tres, a diez, a un sistema entero, sin que la complejidad de cada pieza crezca. MDX toma esa operación —la composición de componentes, que ya dominaba la interfaz de usuario— y la lleva al terreno del contenido, donde antes solo había concatenación de bloques. El resultado es que un documento hereda de golpe todo el poder del modelo de componentes: reutilización, encapsulación, contratos por props, islas de interactividad. Y la distinción entre el componente que se apaga en el build y la isla que vive en el cliente no es un tecnicismo, sino la palanca con la que decides, pieza a pieza, cuánto de ese documento es papel y cuánto es aplicación. Escribir MDX bien es, en el fondo, diseñar esa dosis con intención.
- Crea un componente
Aviso.astroque reciba una proptipoy muestre suslot; impórtalo en un.mdxy úsalo dos veces entre párrafos con tipos distintos. - Anida Markdown dentro de
Avisoprimero pegado a las etiquetas y luego separado por líneas en blanco; contrasta las dos salidas y explica por qué difieren. - Añade un componente de framework —un contador con estado— sin directiva y comprueba que no reacciona; ponle
client:visibley verifica que se convierte en isla. - Razona, para tres piezas de un documento real, cuál dejarías como
.astroestático y cuál hidratarías, justificando cada elección por el JavaScript que envías al cliente.