to format, który łączy Markdown z komponentami JSX. W praktyce oznacza to, że w środku artykułu możesz wstawić interaktywny kalkulator, animowaną grafikę, niestandardową ramkę z ostrzeżeniem albo ankietę — bez opuszczania pliku z treścią.
Czym różni się MDX od Markdown
Markdown to czysty tekst ze składnią dla nagłówków, list, pogrubień, linków i obrazów. MDX dodaje dwie rzeczy:
- Komponenty w treści — możesz napisać
<Callout variant="warning">Uwaga!</Callout>w środku akapitu i to się wyrenderuje jako prawdziwy komponent. - Import innych komponentów — w górze pliku możesz zaimportować React/Vue/Svelte component i użyć go jako JSX.
Przykład MDX:
Wynik: artykuł, w którym masz zarówno zwykłą treść, jak i niestandardowy komponent informacyjny i interaktywny kalkulator.
Jak skonfigurować MDX w Astro i Next.js
W skrócie: w Astro MDX włączasz jedną komendą astro add mdx, w Next.js instalujesz cztery paczki, konfigurujesz createMDX w next.config.mjs i dodajesz wymagany plik mdx-components.tsx. Tu zaczyna się pierwsza różnica między frameworkami.
W Astro instalacja to jedna komenda:
To instaluje @astrojs/mdx i wpisuje integrację mdx() do astro.config.mjs. Typy dla .mdx dostajesz automatycznie przez astro/client, więc nie musisz ręcznie ruszać tsconfig.json. Pliki .mdx w src/pages/ automatycznie stają się stronami (file-based routing), a pliki w src/content/ można wciągnąć do — tak, jak opisałem w artykule o Content Collections.
W Next.js (App Router) instalujesz cztery paczki i konfigurujesz next.config.mjs przez createMDX:
Plus jeden krok, którego w Astro nie ma: mdx-components.tsx w korzeniu projektu jest wymagany — bez niego @next/mdx z App Routerem po prostu nie zadziała:
Różnica filozoficzna: w Astro .mdx to obywatel pierwszej klasy z pudełka, w Next.js MDX jest wpinany jako rozszerzenie kompilatora — stąd więcej kroków, ale i głębsza integracja z React Server Components.
Frontmatter w MDX: Astro natywnie, Next.js przez wtyczkę
MDX w Astro używa YAML — taki sam jak w zwykłym Markdownie. Pola dostępne są później jako frontmatter (w page mode) lub data (w Content Collections).
Zwróć uwagę na {frontmatter.title} — w MDX można używać wyrażeń JavaScript w ciałopodobnej składni JSX. To potężne narzędzie, bo pozwala DRY-ować treść (tytuł z frontmatteru użyty automatycznie).
W kontekście Content Collections rekomenduję jednak trzymać title w szablonie (<h1>{post.data.title}</h1> w [...slug].astro) i w MDX nie duplikować go. Inaczej łatwo o rozjazd.
Jakie komponenty React warto trzymać w blogu MDX
Z doświadczenia — kilka komponentów zarabia na siebie w każdym blogu MDX. Oto te, które trzymam pod ręką.
Callout / Notice
Ramka z ostrzeżeniem, informacją lub wskazówką:
Nazwa propsa (variant) i wartości muszą zgadzać się z wywołaniem w MDX — to najczęstsze źródło „dlaczego mój Callout zawsze wygląda tak samo": komponent czyta jedną nazwę, a artykuł podaje inną, więc zawsze ląduje na wartości domyślnej.
Użycie w MDX:
Code with title
Blok kodu z etykietą/tytułem:
Obraz z opisem
Responsywny obraz z podpisem:
Interaktywne elementy
Realny przykład — prosty kalkulator osadzony w artykule o cenach:
Komponent React dopiero, kiedy user doscrolluje — reszta artykułu zostaje statyczna. Jeśli temat dyrektyw hydracji jest Ci obcy, zajrzyj do artykułu o client directives.
Pamiętaj też, że w Astro 5+ React działa w wersji 19 — jeśli przenosisz komponenty ze starszego projektu, zweryfikuj zgodność bibliotek (część ekosystemu wciąż dogania React 19).
A jak to wygląda w Next.js? Dokładnie odwrotnie. Tu wszystkie komponenty są domyślnie serwerowe (React Server Components), a interaktywność włączasz dyrektywą 'use client' na górze pliku komponentu — nie przy jego użyciu w MDX:
To fundamentalna różnica jest w modelu mentalnym: Astro jest statyczny domyślnie i interaktywność dokładasz punktowo w MDX (client:*), a Next.js jest serwerowy domyślnie i interaktywność deklarujesz w pliku komponentowym (np.'use client'). Efekt jest podobny, ale osiąga się go inaczej (minimum JavaScriptu w przeglądarce). Komponenty z hookami przeglądarki w Next.js też muszą być klienckie; odpowiednikiem Astrowego client:only jest tu po prostu komponent z 'use client', który nie renderuje nic zależnego od window podczas SSR (albo next/dynamic z ssr: false).
Jak zmapować domyślne elementy HTML na własne komponenty (custom mapping)
MDX pozwala zmapować standardowe elementy HTML (np. <h2>, <a>, <img>) na własne komponenty. Używam tego do:
- Dodawania automatycznych „anchor links" do nagłówków (kotwica obok każdego
<h2>). - Otwierania linków zewnętrznych w nowej karcie z
rel="noopener". - Zamiany
<img>na<Image>zastro:assetsdla automatycznej optymalizacji.
Ustawiasz to w layoucie artykułu:
Teraz każdy ## Nagłówek w Twoich artykułach MDX automatycznie dostaje kotwicę, każdy link zewnętrzny — target="_blank", a każdy obraz — optymalizację.
Astro nie ma wbudowanego mechanizmu, który nałożyłby to mapowanie na wszystkie pliki MDX naraz — components przekazujesz do <Content /> przy renderowaniu. Wzorzec dla bazy z dziesiątkami artykułów nie należy do skomplikowanych, ponieważ opiera się na zdefiniowanej mapie globalnej raz w jednym wspólnym layoucie (jak wyżej) i renderowaniu przez niego każdego wpisu. Wtedy ustawienie obowiązuje wszędzie, bo wszystkie artykuły idą tą samą ścieżką renderu.
W Next.js to akurat działa wygodniej. Mapę globalną wpisujesz raz do mdx-components.tsx (tego samego pliku, który i tak jest wymagany), a Next.js stosuje ją automatycznie do każdego pliku MDX bez przekazywania czegokolwiek per-strona:
Mapowanie lokalne (override dla jednej strony) nadal przekazujesz przez prop components do importowanego komponentu MDX, a potem scala się ono z globalnym. To jedno z miejsc, gdzie Next.js wyprzedza Astro.
Optymalizacja obrazów w MDX
Obrazy w MDX optymalizujesz przez <Image> z astro:assets (Astro) lub next/image (Next.js) zamiast surowego <img> — oba generują WebP/AVIF, responsywny srcset, leniwe ładowanie i wymuszają width/height, żeby uniknąć CLS. To obszar, gdzie MDX w Astro ma przewagę nad większością innych setupów, ponieważ zamiast pisać <img src="..."> (brak optymalizacji), używasz <Image>:
Astro w build time:
- Generuje wersje w WebP i AVIF.
- Tworzy responsywne warianty (
srcset). - Dodaje
loading="lazy"dla obrazów poniżej viewportu. - Wstawia explicit
widthiheight, żeby uniknąć .
Dla strony z 80+ artykułami i po kilka obrazów na każdy, automatyczna optymalizacja to różnica między 5 MB a 500 KB ładowanego contentu. Poważnie wpływa na Core Web Vitals.
W Next.js dostajesz to samo przez next/image — z tą wygodą, że mapując img w mdx-components.tsx (jak wyżej) zamieniasz każdy  w treści na zoptymalizowany <Image>, bez ruszania pojedynczych artykułów:
next/image generuje WebP/AVIF, responsywny srcset, leniwe ładowanie i wymusza width/height — dokładnie po to samo, co astro:assets: żeby nie płacić i wagą obrazów.
Typowana baza treści — Content Collections (Astro) i jej odpowiednik w Next.js
Najlepszy setup dla bloga to MDX z walidowanym, typowanym frontmatterem. W Astro to Content Collections. Wtedy masz:
- Pliki
.mdxwsrc/content/blog/. - Schemat Zod walidujący frontmatter.
- Typowane pole
dataw szablonie. - Automatyczne generowanie stron dla każdego artykułu.
Konfiguracja:
Dynamiczna strona:
Teraz każdy plik MDX dodany do src/content/blog/ automatycznie staje się stroną pod /blog/[nazwa-pliku]/ bez żadnej dodatkowej konfiguracji.
Next.js nie ma tego z pudełka — i to jest największa różnica w warstwie treści. Masz trzy ścieżki, od najprostszej do najbardziej zintegrowanej:
- Ręcznie:
fs/globbyczyta pliki,gray-matterparsuje frontmatter, a Ty walidujesz go własnym schematem Zod i renderujesz wapp/blog/[slug]/page.tsxprzezgenerateStaticParams. Najwięcej kontroli, najwięcej kodu. - content-collections (
@content-collections/mdx) — biblioteka, która daje to, co Astrowe Content Collections: typowany, walidowany Zodem frontmatter i wygodne API do listy wpisów. - Contentlayer — popularny historycznie, ale na 2026 jego utrzymanie jest niepewne; do nowych projektów rozważ go ostrożnie.
Schemat Zod wygląda identycznie jak w Astro — różni się tylko to, kto wczytuje pliki: w Astro robi to framework, w Next.js Ty (albo wybrana biblioteka). Mechanizm walidacji i typów jest ten sam.
Pułapki i ograniczenia MDX, o których warto wiedzieć
1. MDX to nie jest „Markdown + HTML". To Markdown + JSX. Niektóre rzeczy wyglądające jak HTML, kompilują się jak JSX — class → className, <br> → <br />, self-closing tagi. Jeśli kopiujesz HTML z innych źródeł, musisz uważać.
2. Nazwa pliku a import. W MDX importy działają względem lokalizacji pliku MDX, nie względem szablonu, który go renderuje. Jeśli przeniesiesz plik MDX do podfolderu, ścieżki importów się rozsypią.
3. Duże MDX = wolne buildy. Jeśli masz plik MDX z 50+ osadzonymi komponentami i tysiącami linii treści, buildy mogą dramatycznie zwolnić. W praktyce nie spotkałem tego problemu przy rozsądnym użyciu (3-5 osadzonych komponentów na artykuł), ale warto mieć to z tyłu głowy.
4. HMR ma limity. W dev mode edycja frontmatteru odświeża stronę w pełni, edycja treści zazwyczaj działa hot. Edycja importowanych komponentów — refresh jest konieczny.
5. Syntax highlighting. Astro używa dla bloków kodu w wersji standardowej. W Next.js dokładasz go jako wtyczkę rehype (rehype-pretty-code oparte o Shiki) w createMDX. W obu przy bardzo dużych artykułach z dziesiątkami bloków kodu rośnie czas buildu: w Astro pomaga expressive-code (lepsze caching), w Next.js warto trzymać highlighting na etapie buildu, a nie runtime.
Standardowy zestaw komponentów MDX dla bloga
W moich projektach blogowych mam standardowy zestaw komponentów:
<Callout>— ramka z info / warning / tip,<Figure>— obraz z podpisem i optymalizacją,<VideoEmbed>— lazy-loaded embed YouTube/Vimeo,<CodeBlock>— kod z nazwą pliku i przyciskiem „Copy",<TableOfContents>— automatyczny spis treści,<RelatedArticles>— komponent z powiązanymi artykułami (przekazywany z szablonu).
Jest ich więcej, ale stosuje je w różnych konfiguracjach. Żaden z nich nie jest przesadnie skomplikowany, a każdy to 20–50 linii. Wszystkie razem sprawiają, że piszę artykuły dwa, trzy razy szybciej, a do tego spójniej.
Zasady użycia komponentów React w treści MDX
MDX bardzo łatwo zmienić w przypadkową aplikację ukrytą w artykule. Żeby tego uniknąć, trzymam się kilku zasad:
- Komponent w MDX powinien wzmacniać zrozumienie treści, a nie zastępować normalny akapit. Nie wciskaj nic na siłę.
- Interaktywny komponent musi mieć jasny powód biznesowy albo edukacyjny. Wszystko musi wynikać z artykułu.
- Komponenty powtarzalne trzymaj globalnie, a nie jako jednorazowe importy w każdym wpisie.
- Nie osadzaj ciężkich bibliotek w artykule bez lazy loadingu (
client:visiblew Astro,next/dynamicw Next.js). - Każdy custom component musi mieć sensowną wersję bez JavaScriptu albo fallback.
