Um JSON vestido de Markdown, com tema claro e escuro
O json-markdown-viewer é um projeto de dois dias que transforma um objeto em bloco de código colorido, e a parte mais chata foi o tema.
![]()
- O projeto tá aberto: github.com/joaomarcostg/json-markdown-viewer
Nem todo projeto é grande. O json-markdown-viewer tem cinco commits, feitos entre 9 e 10 de maio de 2024. Mas tem duas ideias pequenas que valem a conversa.
O que ele faz
Pega um objeto JavaScript e mostra como um bloco de código JSON colorido, com botão pra alternar entre tema claro e escuro. A página de exemplo renderiza um mini perfil:
const personalInfo = {
location: "Brazil",
age: 24,
experience: "4+ years",
stack: ["React", "Node", "Next", "TypeScript"],
};
É Next.js 14 com Tailwind, react-markdown, rehype-highlight e next-themes.
Ideia 1: não escrever um syntax highlighter
O caminho óbvio seria percorrer o objeto e gerar <span> colorido pra cada chave e valor. O projeto faz o contrário. Ele transforma o objeto num texto Markdown com uma cerca de código (aqui sem o try/catch que envolve a linha):
export function getMarkdownStringFromObj(content: Record<string, unknown>): string {
return "```json\n" + JSON.stringify(content, null, 2) + "\n```";
}
A partir daí, quem trabalha é o ecossistema. O react-markdown renderiza o bloco e o rehype-highlight faz o highlight de JSON. Zero lógica de tokenização escrita à mão.
Ideia 2: prefixo nas classes
O rehype-highlight gera classes como hljs-string e hljs-number. Qualquer tema do highlight.js na mesma página pinta essas classes também.
A opção prefix troca o prefixo. O componente recebe ele por prop:
<ReactMarkdown
rehypePlugins={[() => rehypeHighlight({ prefix: classPrefix })]}
children={content}
/>
Com classPrefix="json-md-", as classes viram json-md-string, json-md-attr e assim por diante. O CSS é só do componente, e cada cor tem a versão clara e a escura:
.json-md-string { @apply text-green-600; }
.dark .json-md-string { @apply text-blue-400; }
Componente que carrega o próprio namespace de CSS é um hábito que eu recomendo pra qualquer coisa que vai ser embutida em outro lugar.
A parte chata: hidratação
O último commit se chama “Fix hydration error”. Quem já usou tema com SSR conhece esse.
O next-themes decide o tema no cliente, olhando o localStorage e a preferência do sistema. No servidor, ele não sabe qual é. Então o HTML que o servidor manda e o que o React monta no cliente divergem, e o React reclama.
A correção teve duas partes. Na raiz, suppressHydrationWarning no <html>, porque o next-themes mexe na classe dele antes do React hidratar:
<html lang="en" suppressHydrationWarning>
E no botão de tema, que mostra o tema atual, o componente só renderiza o conteúdo de verdade depois de montar no cliente:
const [isClient, setIsClient] = useState(false);
useEffect(() => { setIsClient(true); }, []);
if (!isClient) return <div>Loading Component</div>;
É o padrão de “espere montar” que a própria documentação do next-themes sugere. Tem um custo: o botão pisca um placeholder no primeiro paint. Pra um botão, aceitável.
O que ficou faltando
O README ainda é o do create-next-app. Não tem entrada de dados: o objeto está fixo no código da página. E o try/catch em volta do JSON.stringify loga e devolve string vazia, o que esconde o caso de objeto com referência circular.
É um projeto de dois dias e está tudo bem ser isso. O código está no GitHub.