MDX
Markdown es un lenguaje de marcado ligero usado para formatear texto. Te 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 añadir 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 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.
Empezando
Instala los paquetes necesarios para renderizar MDX:
npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx
Actualiza el archivo next.config.js
en la raíz de tu proyecto para configurarlo y usar 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, añade cualquier otra configuración de Next.js aquí
}
module.exports = withMDX(nextConfig)
Luego, crea una nueva página MDX dentro del directorio /pages
:
tu-proyecto
├── pages
│ └── my-mdx-page.mdx
└── package.json
Ahora puedes usar markdown e importar componentes React directamente dentro de tu página MDX:
import { MyComponent } from 'my-components'
# 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:
<MyComponent />
Navegar a la ruta /my-mdx-page
debería mostrar tu MDX renderizado.
MDX Remoto
Si tus archivos o contenido markdown/MDX viven 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.
Hay dos paquetes populares de la comunidad para obtener contenido MDX:
Bueno saber: Procede 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 { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'
interface Props {
mdxSource: MDXRemoteSerializeResult
}
export default function RemoteMdxPage({ mdxSource }: Props) {
return <MDXRemote {...mdxSource} />
}
export async function getStaticProps() {
// Texto MDX - puede ser de un archivo local, base de datos, CMS, fetch, etc...
const res = await fetch('https:...')
const mdxText = await res.text()
const mdxSource = await serialize(mdxText)
return { props: { source: mdxSource } }
}
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'
export default function RemoteMdxPage({ mdxSource }) {
return <MDXRemote {...mdxSource} />
}
export async function getStaticProps() {
// Texto MDX - puede ser de un archivo local, base de datos, CMS, fetch, etc...
const res = await fetch('https:...')
const mdxText = await res.text()
const mdxSource = await serialize(mdxText)
return { props: { source: mdxSource } }
}
Navegar a la ruta /my-mdx-page-remote
debería mostrar tu MDX renderizado.
Diseños
Para compartir un diseño alrededor de páginas MDX, crea un componente de diseño:
export default function MdxLayout({ children }: { children: React.ReactNode }) {
// Crea cualquier diseño compartido o estilos aquí
return <div style={{ color: 'blue' }}>{children}</div>
}
export default function MdxLayout({ children }) {
// Crea cualquier diseño compartido o estilos aquí
return <div style={{ color: 'blue' }}>{children}</div>
}
Luego, importa el componente de diseño en la página MDX, envuelve el contenido MDX en el diseño y expórtalo:
import MdxLayout from '../components/mdx-layout'
# Bienvenido a mi página MDX!
export default function MDXPage({ children }) {
return <MdxLayout>{children}</MdxLayout>;
}
Plugins de Remark y Rehype
Opcionalmente puedes proveer 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, añade cualquier otra configuración de Next.js aquí
}
const withMDX = createMDX({
// Añade plugins de markdown aquí, como desees
options: {
remarkPlugins: [remarkGfm],
rehypePlugins: [],
},
})
// Combina la configuración MDX con la configuración de Next.js
export default withMDX(nextConfig)
Frontmatter
Frontmatter es un emparejamiento 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 añadir frontmatter a tu contenido MDX, como:
Para acceder a metadatos de página con @next/mdx
, puedes exportar un objeto meta desde dentro del archivo .mdx
:
export const meta = {
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 quieres estilizar tus propios elementos para un aspecto personalizado en tu sitio web o aplicación, puedes pasar shortcodes. Estos son tus propios componentes personalizados que se mapean a elementos HTML
.
Para hacer esto, crea un archivo mdx-components.tsx
en la raíz de tu aplicación (la carpeta padre de pages/
o src/
) y añade elementos personalizados:
import type { MDXComponents } from 'mdx/types'
import Image from 'next/image'
// Este archivo te permite proveer componentes React personalizados
// para ser usados en archivos MDX. Puedes importar y usar cualquier
// componente React que quieras, incluyendo estilos en línea,
// componentes de otras librerías, y más.
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
// Permite personalizar componentes integrados, ej. para añadir estilos.
h1: ({ children }) => <h1 style={{ fontSize: '100px' }}>{children}</h1>,
img: (props) => (
<Image
sizes="100vw"
style={{ width: '100%', height: 'auto' }}
{...props}
/>
),
...components,
}
}
import Image from 'next/image'
// Este archivo te permite proveer componentes React personalizados
// para ser usados en archivos MDX. Puedes importar y usar cualquier
// componente React que quieras, incluyendo estilos en línea,
// componentes de otras librerías, y más.
export function useMDXComponents(components) {
return {
// Permite personalizar componentes integrados, ej. para añadir estilos.
h1: ({ children }) => <h1 style={{ fontSize: '100px' }}>{children}</h1>,
img: (props) => (
<Image
sizes="100vw"
style={{ width: '100%', height: 'auto' }}
{...props}
/>
),
...components,
}
}
Profundizando: ¿Cómo se transforma markdown a HTML?
React no entiende markdown de forma nativa. El texto plano markdown necesita primero ser transformado a HTML. Esto puede lograrse 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 a 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,
},
})