El prop components al renderizar
Mapear los elementos HTML que genera el Markdown —h1, a, pre— a componentes propios pasando un objeto al prop components al renderizar un documento MDX: cómo reemplazar de forma global la presentación sin tocar el contenido, desde una colección con render y Content o importando el módulo, y la frontera entre definir el mapa dentro del fichero o desde fuera.
El prop components: reemplazar el HTML generado
Cuando Astro compila el Markdown de un documento, cada construcción se traduce a su etiqueta HTML canónica. Ese <h1> o ese <a> son genéricos: no saben de tu diseño ni de tu comportamiento. El prop components cambia eso. Al renderizar el documento, le pasas un objeto cuyas claves son nombres de etiquetas HTML y cuyos valores son componentes tuyos; a partir de ahí, cada vez que el Markdown produzca esa etiqueta, Astro renderiza tu componente en su lugar.
---import { Content } from '../content/nota.mdx';import Enlace from '../components/Enlace.astro';---<Content components={{ a: Enlace }} />Con ese único mapa, todos los enlaces del documento —cada [texto](url) del Markdown— pasan a renderizarse con tu componente Enlace, que recibe las mismas props que habría recibido el <a> nativo: su href, su contenido como slot, sus atributos. El contenido no cambió ni una letra; cambió quién dibuja sus enlaces. Esa es la esencia del mecanismo: una intervención global en la presentación, declarada desde fuera del texto.
Conviene apreciar lo que esto evita. Sin el prop components, personalizar cómo se ven los enlaces de un documento obligaría a editar el documento mismo —reemplazar cada enlace de Markdown por una etiqueta de componente— y a repetir esa cirugía en cada fichero y cada vez que cambiara el diseño. El mapa externo hace innecesaria esa intromisión: la prosa sigue siendo prosa limpia, con su sintaxis de Markdown intacta, y toda la decisión de presentación se concentra en un objeto que vive fuera. El autor no tiene que saber que sus enlaces se están enriqueciendo, y quien los enriquece no tiene que tocar una línea de contenido.
Mapear h1, a y pre a componentes propios
El mapa admite tantas entradas como etiquetas quieras interceptar. Las tres más útiles en la práctica son el encabezado, el enlace y el bloque de código, porque cada una gana algo concreto al convertirse en componente: el h1 puede añadir un ancla enlazable, el a puede distinguir enlaces externos, y el pre puede envolver el código en un botón de copiar.
---import { getEntry, render } from 'astro:content';import Titulo from '../components/Titulo.astro';import Enlace from '../components/Enlace.astro';import Bloque from '../components/Bloque.astro';const entrada = await getEntry('docs', Astro.params.slug);const { Content } = await render(entrada);---<Content components={{ h1: Titulo, a: Enlace, pre: Bloque }} />Cada componente del mapa recibe las props del elemento que sustituye y su contenido por el slot. Un Titulo recibe el texto del encabezado y puede generar un id para enlazarlo; un Enlace recibe el href y decide si abrir en pestaña nueva según sea externo; un Bloque recibe el código ya resaltado y lo rodea de su interfaz. Las claves del mapa son los nombres de etiqueta en minúscula —h1 a h6, a, p, pre, code, img, blockquote, ul, ol, li, table— y cubren toda la salida del Markdown.
Un Enlace que distingue enlaces externos ilustra el patrón completo. Recibe el href que el Markdown generó, decide por su forma si apunta fuera del sitio y reenvía todo a un <a> real, añadiendo los atributos que quiera:
---// src/components/Enlace.astrointerface Props { href: string; }const { href } = Astro.props;const externo = href.startsWith('http');---<a href={href} target={externo ? '_blank' : undefined} rel={externo ? 'noopener' : undefined}> <slot /></a>El componente no rompe nada de lo que el autor escribió: cada [texto](url) sigue siendo un enlace con su destino y su contenido. Solo añade comportamiento —abrir fuera, marcar la relación— que el <a> genérico no tenía. Ese es el equilibrio que busca un buen interceptor: enriquecer sin alterar la semántica que el documento declaró.
Desde una colección o desde un import
Hay dos escenarios donde pasas el prop components, y ambos comparten la misma idea. El primero es renderizar una entrada de una colección: recuperas la entrada, la conviertes en un componente Content con render, y le pasas el mapa. El segundo es importar directamente un .mdx como módulo, que expone un componente Content al que igualmente pasas components. En los dos casos, el mapa viaja como prop en el punto de renderizado.
---// Importando el módulo MDX directamenteimport { Content } from '../content/guia.mdx';import Enlace from '../components/Enlace.astro';import Bloque from '../components/Bloque.astro';---<Content components={{ a: Enlace, pre: Bloque }} />La ventaja de pasar el mapa desde el punto de renderizado es el alcance: un solo objeto, definido una vez en la ruta que pinta el contenido, gobierna la presentación de todas las entradas que pasen por ella. Cambiar cómo se ven los enlaces de un blog entero es editar una línea en la plantilla que renderiza las entradas, no revisar cientos de documentos. La presentación se centraliza en el borde, y el contenido permanece limpio de decisiones de estilo.
Cuando ese mapa crece —encabezados con ancla, enlaces externos, bloques de código con botón de copiar, imágenes optimizadas— conviene extraerlo a un módulo compartido y reutilizarlo en cada ruta que renderice contenido. Un fichero que exporte el objeto components y se importe donde haga falta convierte la presentación del contenido en una sola fuente de verdad: un cambio de política —por ejemplo, marcar todos los enlaces externos con un icono— se hace una vez y se propaga a cada página que use ese mapa, sin duplicar el objeto por las plantillas. La misma disciplina que aplicas a los estilos globales se aplica aquí al mapa de componentes.
Global frente a local: dónde definir el mapa
Existe una alternativa a pasar el mapa desde fuera: dentro de un .mdx, puedes importar componentes y usarlos con nombre en el propio texto, o incluso reasignar etiquetas localmente. La diferencia es de alcance y de responsabilidad. El prop components desde fuera es global y externo: una decisión de quien renderiza, que se aplica a todo el documento sin que este se entere. Usar componentes con nombre dentro del .mdx es local y explícito: una decisión del autor, visible en el texto.
El criterio para elegir es la naturaleza de la decisión. Si quieres que todos los enlaces de todos los documentos se comporten igual, eso es una política de presentación y vive en el mapa externo. Si quieres una alerta concreta en el segundo párrafo de un documento concreto, eso es contenido y vive en el texto. Confundirlos lleva a duplicar: mapear a mano en cada fichero lo que un solo mapa externo resolvería, o esconder en un mapa global lo que era una pieza única de un documento.