Capítulo 54 de 456

MDX

Core Idea

MDX é markdown com suporte a JSX embutido, permitindo escrever conteúdo com componentes React interativos. Next.js suporta MDX local (via @next/mdx) tanto como páginas por file-based routing quanto via import direto, com Server Components por padrão no App Router.

Key Concepts

  • @next/mdx: pacote que configura o Next.js para processar .md/.mdx como páginas, rotas ou imports; fonte de dados são arquivos locais.
  • pageExtensions: opção do next.config.mjs que precisa incluir md/mdx para que esses arquivos virem páginas/rotas.
  • mdx-components.tsx: arquivo obrigatório na raiz do projeto (ou src/) que define componentes MDX globais via useMDXComponents(); sem ele, @next/mdx não funciona no App Router.
  • File-based routing vs import: MDX pode virar página diretamente (app/mdx-page/page.mdx) ou ser importado num componente React (import Welcome from '@/markdown/welcome.mdx').
  • Dynamic imports de MDX: usar import(@/content/${slug}.mdx) dentro de uma rota dinâmica, combinado com generateStaticParams e dynamicParams = false para 404 em slugs não pré-gerados.
  • Local vs global styles: componentes globais em mdx-components.tsx afetam todos os MDX; componentes locais passados via prop components no import sobrescrevem os globais só naquela página.
  • Shared layouts: layout do App Router (app/mdx-page/layout.tsx) aplica estilo compartilhado a todas as páginas MDX daquele segmento.
  • Frontmatter: @next/mdx não suporta nativamente; alternativas são remark-frontmatter, remark-mdx-frontmatter, gray-matter. Em vez disso, @next/mdx permite export const metadata = {...} direto no arquivo .mdx, importável de fora.
  • remark/rehype plugins: transformam o conteúdo MDX (ex.: remark-gfm para GitHub Flavored Markdown); exigem next.config.mjs/.ts (ESM only).
  • Plugins com Turbopack: precisam ser referenciados por string (nome do pacote), não como função JS importada, porque funções não podem ser passadas ao Rust; plugins sem opções serializáveis ainda não funcionam com Turbopack.
  • mdxRs (experimental): compilador MDX baseado em Rust, não recomendado para produção; aceita config de jsxRuntime, jsxImportSource, providerImportSource, mdxType (gfm|commonmark).

Code Examples

pnpm add @next/mdx @mdx-js/loader @mdx-js/react @types/mdx
  • O que demonstra: dependências necessárias para MDX no Next.js.
// next.config.mjs
import createMDX from '@next/mdx'

const nextConfig = {
  pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
}

const withMDX = createMDX({})
export default withMDX(nextConfig)
  • O que demonstra: configuração base que registra .mdx como extensão de página válida.
// mdx-components.tsx (obrigatório no App Router)
import type { MDXComponents } from 'mdx/types'

const components: MDXComponents = {}

export function useMDXComponents(): MDXComponents {
  return components
}
  • O que demonstra: arquivo raiz obrigatório para o MDX funcionar com App Router.
// app/blog/[slug]/page.tsx — import dinâmico de MDX
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params
  const { default: Post } = await import(`@/content/${slug}.mdx`)
  return <Post />
}

export function generateStaticParams() {
  return [{ slug: 'welcome' }, { slug: 'about' }]
}

export const dynamicParams = false
  • O que demonstra: rota dinâmica que carrega MDX por slug e restringe a slugs pré-gerados (404 fora da lista).
// mdx-components.tsx — customização global (h1 e img)
const components = {
  h1: ({ children }) => <h1 style={{ color: 'red', fontSize: '48px' }}>{children}</h1>,
  img: (props) => <Image sizes="100vw" style={{ width: '100%', height: 'auto' }} {...props} />,
} satisfies MDXComponents
  • O que demonstra: mapeamento de elementos HTML gerados pelo markdown para componentes customizados (aqui, next/image no lugar de <img>).
export const metadata = {
  author: 'John Doe',
}

# Blog post
  • O que demonstra: metadata exportada diretamente do .mdx, importável em page.tsx via import BlogPost, { metadata } from '@/content/blog-post.mdx'.
// next.config.mjs — plugins com Turbopack (strings, não funções)
const withMDX = createMDX({
  options: {
    remarkPlugins: ['remark-gfm', ['remark-toc', { heading: 'The Table' }]],
    rehypePlugins: ['rehype-slug', ['rehype-katex', { strict: true, throwOnError: true }]],
  },
})
  • O que demonstra: sintaxe de string exigida pelo Turbopack para plugins remark/rehype.

Anti-patterns

  • Omitir mdx-components.tsx no App Router: @next/mdx simplesmente não funciona sem esse arquivo.
  • Esperar suporte a frontmatter nativo: @next/mdx não processa YAML frontmatter por padrão; é preciso plugin extra ou usar export const metadata.
  • Passar função JS como plugin remark/rehype no Turbopack: falha porque funções não são serializáveis para o compilador Rust; usar string com nome do pacote.
  • Usar mdxRs em produção: ainda experimental, não recomendado.
  • Esquecer .mdx na extensão do import dinâmico: import(@/content/${slug}.mdx) precisa da extensão explícita.

Key Takeaways

  1. mdx-components.tsx na raiz é obrigatório para MDX funcionar com App Router.
  2. MDX pode virar página via file-based routing ou ser importado; imports dinâmicos com generateStaticParams/dynamicParams = false permitem prerender rotas de conteúdo por slug.
  3. Estilos/components podem ser globais (mdx-components.tsx), locais (prop components no import) ou via layout compartilhado (inclusive com @tailwindcss/typography).
  4. Frontmatter não é nativo; usar export const metadata no MDX ou plugins remark de frontmatter.
  5. remark/rehype plugins customizam a transformação; com Turbopack, plugins são passados como string (não função), e sem opções serializáveis ainda não funcionam.

Connects To

  • mdx-components.tsx (file convention): referência detalhada do arquivo obrigatório.
  • generateStaticParams: usado para prerender rotas MDX dinâmicas.
  • next/image: comumente mapeado a img nos componentes MDX customizados.
  • Metadata and OG images: MDX pages suportam a Metadata API do App Router.