Como Construímos um Blog com MDX, Next.js e Componentes Interativos

#devlog#nextjs#mdx#arquitetura
Como Construímos um Blog com MDX, Next.js e Componentes Interativos

Este post é para desenvolvedores. Vamos abrir o capô do sistema de blog que você está lendo agora e explicar cada decisão de arquitetura — desde como os arquivos .mdx viram páginas até como um componente interativo é injetado no meio do texto sem comprometer a performance do servidor.

A Stack

O blog roda sobre o Next.js App Router (v16) e usa duas dependências centrais:

  • next-mdx-remote — compila MDX em React Server Components, sem necessidade de configurar loaders no bundler.
  • gray-matter — extrai o frontmatter YAML do topo de cada arquivo .mdx.

Não usamos @next/mdx (que transforma .mdx diretamente em páginas dentro de app/) porque precisávamos de controle programático para listar, ordenar e filtrar posts — algo que exige ler os arquivos como dados, não como rotas.

Estrutura de Arquivos

src/
├── content/
│   └── blog/
│       ├── hello-world.mdx        ← este arquivo
│       └── outro-post.mdx
├── lib/
│   └── blog.ts                    ← lê e parseia os .mdx
├── components/
│   ├── MDXComponents.tsx          ← mapeamento de componentes
│   ├── AmortizationDemo.tsx       ← componente interativo ('use client')
│   ├── BlogHeader.tsx
│   └── BlogFooter.tsx
└── app/
    └── blog/
        ├── page.tsx               ← listagem (/blog)
        └── [slug]/
            └── page.tsx           ← post individual (/blog/[slug])

Os posts ficam em src/content/blog/, completamente separados do código da aplicação. O nome do arquivo é o slug da URL: hello-world.mdx vira /blog/hello-world.

Como o Conteúdo é Carregado

O módulo blog.ts expõe duas funções:

  • getBlogPosts() — lê todos os .mdx do diretório, extrai o frontmatter com gray-matter e retorna uma lista de metadados ordenada por data (mais recente primeiro).
  • getPostBySlug(slug) — lê um arquivo específico e retorna o conteúdo bruto + metadados.

Ambas rodam no servidor (Node.js fs), nunca no browser. O Next.js chama essas funções em build time (para generateStaticParams) e em request time (para renderização).

Compilação do MDX

No componente da página [slug]/page.tsx, o conteúdo bruto do .mdx é passado para o MDXRemote do next-mdx-remote/rsc:

import { MDXRemote } from 'next-mdx-remote/rsc';

<MDXRemote
  source={post.content}
  components={mdxComponents}
/>

O MDXRemote compila o markdown em JSX no servidor, gerando HTML estático. Isso significa que o corpo do artigo não precisa de JavaScript no client — a menos que um componente específico exija interatividade.

Componentes Customizados

O arquivo MDXComponents.tsx mapeia cada elemento HTML nativo para um componente React estilizado:

export const mdxComponents = {
  h2: (props) => <h2 className="font-serif font-semibold text-foreground ..." {...props} />,
  p:  (props) => <p className="text-muted-foreground leading-[1.8] ..." {...props} />,
  blockquote: (props) => <blockquote className="border-l-2 border-primary/40 ..." {...props} />,
  // ...
  AmortizationDemo,  // componente interativo
};

Todas as classes usam CSS variables do design system (text-foreground, bg-card, border-primary), não cores hard-coded. Isso garante que se o tema mudar, o blog muda junto.

Componentes Interativos

Componentes como o AmortizationDemo abaixo são marcados com 'use client' e carregam JavaScript apenas quando necessários. No MDX, basta usá-los como uma tag:

<AmortizationDemo />

E o resultado é um componente React completo, renderizado inline no artigo:

Simulação Rápida (PRICE)

R$ 250.000,00
120 meses
9.50%
Parcela Estimada
R$ 3234.94

A hidratação acontece apenas para esse componente. O restante do artigo continua sendo HTML estático.

Como Criar um Novo Post

Crie um arquivo .mdx em src/content/blog/ com o frontmatter obrigatório:

---
title: "Título do Post"
date: "2026-07-20"
description: "Resumo que aparece na listagem."
tags: ["tag1", "tag2"]
---

Escreva o conteúdo em Markdown normal. Para usar um componente interativo, registre-o no objeto mdxComponents em MDXComponents.tsx e chame-o pelo nome no corpo do texto.

Não é necessário reiniciar o servidor de desenvolvimento — o hot reload detecta novos arquivos automaticamente.

Como Adicionar um Novo Componente Interativo

O fluxo completo:

  1. Crie o componente em src/components/ com a diretiva 'use client' no topo.
  2. Importe-o em MDXComponents.tsx e adicione-o ao objeto mdxComponents.
  3. Use <NomeDoComponente /> no corpo do seu .mdx.

O MDXRemote resolve a referência automaticamente na hora da compilação. Não é necessário importar nada dentro do arquivo .mdx — o mapeamento em mdxComponents é o catálogo de tudo que está disponível.


O que acha de testar essa solução no seu próprio projeto? Navegue pelos posts do Visconde, aprenda sobre finanças, e veja como criamos um sistema de baixo custo para ter nosso próprio blog.