Všechny příspěvky

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.