Markdown y MDX
Markdown es un lenguaje de marcado ligero utilizado para formatear texto. Permite escribir usando sintaxis de texto plano y convertirlo a HTML estructuralmente válido. Es comúnmente usado para escribir contenido en sitios web y blogs.
Escribes...
I **love** using [Next.js](https://nextjs.org/)
Salida:
<p>I <strong>love</strong> using <a href="https://nextjs.org/">Next.js</a></p>
MDX es un superconjunto de markdown que te permite escribir JSX directamente en tus archivos markdown. Es una forma poderosa de agregar interactividad dinámica e incrustar componentes React dentro de tu contenido.
Next.js puede soportar tanto contenido MDX local dentro de tu aplicación, como archivos MDX remotos obtenidos dinámicamente en el servidor. El plugin de Next.js maneja la transformación de markdown y componentes React a HTML, incluyendo soporte para su uso en Componentes del Servidor (el predeterminado en App Router).
@next/mdx
El paquete @next/mdx
se usa para configurar Next.js y que pueda procesar markdown y MDX. Obtiene datos de archivos locales, permitiéndote crear páginas con extensión .mdx
, directamente en tus directorios /pages
o /app
.
Veamos cómo configurar y usar MDX con Next.js.
Comenzando
Instala los paquetes necesarios para renderizar MDX:
npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx
Crea un archivo mdx-components.tsx
en la raíz de tu aplicación (src/
o la carpeta padre de app/
):
Nota importante:
mdx-components.tsx
es requerido para usar MDX con App Router y no funcionará sin él.
import type { MDXComponents } from 'mdx/types'
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...components,
}
}
export function useMDXComponents(components) {
return {
...components,
}
}
Actualiza el archivo next.config.js
en la raíz de tu proyecto para configurarlo y que use MDX:
const withMDX = require('@next/mdx')()
/** @type {import('next').NextConfig} */
const nextConfig = {
// Configura `pageExtensions` para incluir archivos MDX
pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'],
// Opcionalmente, agrega cualquier otra configuración de Next.js aquí
}
module.exports = withMDX(nextConfig)
Luego, crea una nueva página MDX dentro del directorio /app
:
tu-proyecto
├── app
│ └── mi-pagina-mdx
│ └── page.mdx
└── package.json
Ahora puedes usar markdown e importar componentes React directamente dentro de tu página MDX:
import { MiComponente } from 'mis-componentes'
# ¡Bienvenido a mi página MDX!
Este es un texto en **negrita** y _cursiva_.
Esta es una lista en markdown:
- Uno
- Dos
- Tres
Mira mi componente React:
<MiComponente />
Navegar a la ruta /mi-pagina-mdx
debería mostrar tu MDX renderizado.
MDX Remoto
Si tus archivos o contenido markdown/MDX están en otro lugar, puedes obtenerlos dinámicamente en el servidor. Esto es útil para contenido almacenado en una carpeta local separada, CMS, base de datos o cualquier otro lugar. Un paquete popular de la comunidad para este caso es next-mdx-remote
.
Nota importante: Proceda con precaución. MDX se compila a JavaScript y se ejecuta en el servidor. Solo debes obtener contenido MDX de una fuente confiable, de lo contrario esto puede llevar a ejecución remota de código (RCE).
El siguiente ejemplo usa next-mdx-remote
:
import { MDXRemote } from 'next-mdx-remote/rsc'
export default async function PaginaMdxRemota() {
// Texto MDX - puede ser de un archivo local, base de datos, CMS, fetch, etc...
const res = await fetch('https://...')
const markdown = await res.text()
return <MDXRemote source={markdown} />
}
import { MDXRemote } from 'next-mdx-remote/rsc'
export default async function PaginaMdxRemota() {
// Texto MDX - puede ser de un archivo local, base de datos, CMS, fetch, etc...
const res = await fetch('https://...')
const markdown = await res.text()
return <MDXRemote source={markdown} />
}
Navegar a la ruta /mi-pagina-mdx-remota
debería mostrar tu MDX renderizado.
Diseños
Para compartir un diseño entre páginas MDX, puedes usar el soporte de diseños integrado con App Router.
export default function DisenoMdx({ children }: { children: React.ReactNode }) {
// Crea cualquier diseño compartido o estilos aquí
return <div style={{ color: 'blue' }}>{children}</div>
}
export default function DisenoMdx({ children }) {
// Crea cualquier diseño compartido o estilos aquí
return <div style={{ color: 'blue' }}>{children}</div>
}
Plugins Remark y Rehype
Opcionalmente puedes proporcionar plugins remark
y rehype
para transformar el contenido MDX.
Por ejemplo, puedes usar remark-gfm
para soportar GitHub Flavored Markdown.
Dado que el ecosistema remark
y rehype
es solo ESM, necesitarás usar next.config.mjs
como archivo de configuración.
import remarkGfm from 'remark-gfm'
import createMDX from '@next/mdx'
/** @type {import('next').NextConfig} */
const nextConfig = {
// Configura `pageExtensions` para incluir archivos MDX
pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'],
// Opcionalmente, agrega cualquier otra configuración de Next.js aquí
}
const withMDX = createMDX({
// Agrega plugins markdown aquí, como desees
options: {
remarkPlugins: [remarkGfm],
rehypePlugins: [],
},
})
// Envuelve MDX y la configuración de Next.js entre sí
export default withMDX(nextConfig)
Frontmatter
Frontmatter es un par clave/valor similar a YAML que puede usarse para almacenar datos sobre una página. @next/mdx
no soporta frontmatter por defecto, aunque hay muchas soluciones para agregar frontmatter a tu contenido MDX, como:
Para acceder a metadatos de página con @next/mdx
, puedes exportar un objeto metadata desde dentro del archivo .mdx
:
export const metadata = {
author: 'John Doe',
}
# Mi página MDX
Elementos Personalizados
Uno de los aspectos agradables de usar markdown, es que se mapea a elementos HTML
nativos, haciendo que escribir sea rápido e intuitivo:
Esta es una lista en markdown:
- Uno
- Dos
- Tres
Lo anterior genera el siguiente HTML
:
<p>Esta es una lista en markdown:</p>
<ul>
<li>Uno</li>
<li>Dos</li>
<li>Tres</li>
</ul>
Cuando quieras diseñar tus propios elementos para un aspecto personalizado en tu sitio web o aplicación, puedes usar shortcodes. Estos son tus propios componentes personalizados que se mapean a elementos HTML
.
Para hacer esto, abre el archivo mdx-components.tsx
en la raíz de tu aplicación y agrega elementos personalizados:
import type { MDXComponents } from 'mdx/types'
import Image, { ImageProps } from 'next/image'
// Este archivo te permite proporcionar componentes React personalizados
// para usar en archivos MDX. Puedes importar y usar cualquier
// componente React que desees, incluyendo estilos en línea,
// componentes de otras bibliotecas, y más.
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
// Permite personalizar componentes integrados, ej. para agregar estilos.
h1: ({ children }) => <h1 style={{ fontSize: '100px' }}>{children}</h1>,
img: (props) => (
<Image
sizes="100vw"
style={{ width: '100%', height: 'auto' }}
{...(props as ImageProps)}
/>
),
...components,
}
}
import Image from 'next/image'
// Este archivo te permite proporcionar componentes React personalizados
// para usar en archivos MDX. Puedes importar y usar cualquier
// componente React que desees, incluyendo estilos en línea,
// componentes de otras bibliotecas, y más.
export function useMDXComponents(components) {
return {
// Permite personalizar componentes integrados, ej. para agregar estilos.
h1: ({ children }) => <h1 style={{ fontSize: '100px' }}>{children}</h1>,
img: (props) => (
<Image
sizes="100vw"
style={{ width: '100%', height: 'auto' }}
{...props}
/>
),
...components,
}
}
Análisis Profundo: ¿Cómo se transforma markdown a HTML?
React no entiende nativamente markdown. El texto plano markdown necesita primero transformarse a HTML. Esto se puede lograr con remark
y rehype
.
remark
es un ecosistema de herramientas alrededor de markdown. rehype
es lo mismo, pero para HTML. Por ejemplo, el siguiente fragmento de código transforma markdown a HTML:
import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import rehypeSanitize from 'rehype-sanitize'
import rehypeStringify from 'rehype-stringify'
main()
async function main() {
const file = await unified()
.use(remarkParse) // Convertir en AST de markdown
.use(remarkRehype) // Transformar a AST de HTML
.use(rehypeSanitize) // Sanitizar entrada HTML
.use(rehypeStringify) // Convertir AST a HTML serializado
.process('¡Hola, Next.js!')
console.log(String(file)) // <p>¡Hola, Next.js!</p>
}
El ecosistema remark
y rehype
contiene plugins para resaltado de sintaxis, enlaces a encabezados, generar tabla de contenidos, y más.
Cuando usas @next/mdx
como se mostró arriba, no necesitas usar remark
o rehype
directamente, ya que se maneja por ti. Lo describimos aquí para un entendimiento más profundo de lo que el paquete @next/mdx
está haciendo internamente.
Usando el compilador MDX basado en Rust (Experimental)
Next.js soporta un nuevo compilador MDX escrito en Rust. Este compilador es aún experimental y no se recomienda para uso en producción. Para usar el nuevo compilador, necesitas configurar next.config.js
cuando lo pasas a withMDX
:
module.exports = withMDX({
experimental: {
mdxRs: true,
},
})