MDX: Markdown, který mluví Reactem
Pokud jste někdy psali dokumentaci nebo blog v Markdownu, víte jak příjemné to je. Žádné HTML entity, přirozená syntaxe, čitelné i v plain textu.
Jenže Markdown má limity. Co když chcete uprostřed textu zobrazit vlastní komponentu, interaktivní demo nebo zvýrazněný blok s poznámkou? Přesně proto vznikl MDX.
Co je MDX
MDX je souborový formát, který kombinuje Markdown s JSX. Píšete klasický Markdown — nadpisy, odstavce, seznamy, code bloky — ale kdykoli potřebujete, přidáte přímo JSX komponentu:
## Normální nadpis
Toto je běžný odstavec v Markdownu.
<Callout>
A toto je React komponenta přímo v textu.
</Callout>
Výsledek se kompiluje do React komponent. Veškerý Markdown se přeloží na HTML elementy, JSX zůstane jako je. Není potřeba nic importovat přímo v .mdx souboru — komponenty předáte z kódu při kompilaci.
Frontmatter
Každý MDX soubor může začínat YAML frontmatter blokem ohraničeným ---. Ten slouží k uložení metadat — nadpis, datum, tagy, stav publikování:
---
title: "Název příspěvku"
date: "2025-10-08"
tags: ["MDX", "React"]
published: true
---
Obsah začíná tady.
Na serveru si frontmatter přečtete knihovnou gray-matter, nebo přímo volbou parseFrontmatter: true v kompilátoru. Výsledkem je typovaný JavaScript objekt — žádná magie.
Kompilace v Next.js
V Next.js App Routeru se MDX nejlépe kompiluje přes balíček next-mdx-remote. Pro React Server Components použijete variantu z podbalíčku /rsc:
import { compileMDX } from 'next-mdx-remote/rsc'
const { content } = await compileMDX({
source: rawMdxString,
options: { parseFrontmatter: false },
components: {
Callout,
},
})
return <div className="prose">{content}</div>
Funkce compileMDX je asynchronní — proto musí být volající komponenta označena jako async. Vrací zkompilovaný JSX připravený k renderování.
Vlastní komponenty
Tady začíná skutečná síla MDX. Zaregistrujete komponentu jednou v serverové komponentě a pak ji používáte v libovolném .mdx souboru:
// BlogPost.tsx — serverová komponenta
const { content } = await compileMDX({
source: post.content,
components: {
Callout,
CodeSandbox,
InteractiveDemo,
},
})
// libovolný blog post
Ukázka výsledku:
<InteractiveDemo data={[10, 42, 7, 99]} />
<Callout type="warning">
Tato funkce je experimentální a API se může změnit.
</Callout>
Komponenty mohou být libovolně složité — server i client, s lokálním stavem, animacemi, fetch voláními. MDX je nezajímá, jak jsou implementované.
Přepisování HTML elementů
Kromě vlastních komponent můžete přepsat výchozí HTML elementy, které Markdown generuje. Chcete, aby všechny <a> v obsahu otvíraly nové okno? Jednoduché:
const { content } = await compileMDX({
source: post.content,
components: {
a: ({ href, children }) => (
<a href={href} target="_blank" rel="noopener noreferrer">
{children}
</a>
),
// přepsání pre pro syntax highlighting
pre: ({ children }) => (
<SyntaxHighlighter>{children}</SyntaxHighlighter>
),
},
})
Stejně tak lze přepsat h1, h2, img, blockquote — prostě libovolný HTML element, který Markdown produkuje.
Kdy MDX dává smysl
MDX přidává vrstvu složitosti oproti čistému Markdownu. Má smysl tam, kde tu složitost ospravedlní:
- Technický blog s code ukázkami a vloženými demy
- Dokumentace, kde chcete vedle textu živé příklady komponent
- Design system docs, kde dokumentujete vlastní komponenty — a rovnou je zobrazíte
- Výukový obsah s interaktivními prvky (kvízy, animace)
Pro čistě textový obsah bez komponent je obyčejný Markdown dostačující a jednodušší na správu.
Tento blog funguje přesně na této kombinaci: soubory .mdx v repozitáři, gray-matter pro frontmatter, next-mdx-remote/rsc pro kompilaci. Komponenta Callout, kterou jste viděli v celém tomto příspěvku, je předána přesně tím způsobem popsaným výše.