---
title: "20 — Markdown and MDX — Content with Components Inside"
uid: markdown-mdx
tags: ["roadmap:nextjs", "mdx", "content", "app-router", "nextjs", "markdown"]
excerpt: "Markdown is a lightweight markup language for text; MDX is its superset that embeds React components inside the prose. Next.js handles both local and remote sources."
date: 2026-08-13T03:27:59+0000
source: https://www.aveshina.my.id/en/blog/markdown-mdx
---

The content layer for blogs, docs, and prose-driven sites has two names that differ by one capability. The model that clicked: **Markdown is a lightweight markup language that turns plain-text syntax into structurally valid HTML; MDX is its superset that lets me write JSX directly inside the markdown, embedding React components within the prose** [1]. Next.js supports both local MDX (files in the project) and remote MDX (fetched dynamically), with the App Router handling the transform so MDX works in Server Components by default.

## Markdown — the plain-text baseline

Markdown lets me write structured content in plain text — # for headings, **bold**, lists, links — that converts to valid HTML. It's the lingua franca of blog posts, README files, and documentation because it's readable as text and renders as structured markup. For pure-prose content with no interactivity, plain Markdown is enough.

## MDX — components inside prose

MDX adds JSX to Markdown. The payoff is content that can include interactive or data-driven components: a chart inside a blog post, a live code playground, an embedded widget. Instead of choosing between "a markdown page" and "a React page," MDX lets the prose carry components where they're needed.

```
# My Post

Here's a paragraph of prose.

<Chart data={salesData} />

And here's more prose after the chart.
```

The Next.js plugin transforms MDX into React components at build time, so the result is a regular component tree that renders server-side in the App Router [1].

```figure
<svg viewBox="0 0 720 260" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="Markdown vs MDX pipeline. Top: a .md file compiles to plain HTML (headings, paragraphs, lists) — prose only. Bottom: a .mdx file containing the same prose plus a <Chart> JSX element compiles to HTML with the prose rendered AND the interactive React component embedded in place.">
  <defs>
    <marker id="mdxarrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
      <path d="M0,0 L10,5 L0,10 z" fill="#64748b"/>
    </marker>
  </defs>
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">

    <!-- Markdown -->
    <text x="360" y="22" font-size="12" font-weight="700" fill="#422006" text-anchor="middle">Markdown — prose only</text>
    <rect x="40" y="38" width="180" height="60" rx="8" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.5"/>
    <text x="130" y="58" font-size="10" font-family="ui-monospace, monospace" fill="#422006" text-anchor="middle">post.md</text>
    <text x="130" y="78" font-size="9" font-family="ui-monospace, monospace" fill="#422006" text-anchor="middle"># Title · prose · lists</text>
    <line x1="220" y1="68" x2="290" y2="68" stroke="#64748b" stroke-width="1.5" marker-end="url(#mdxarrow)"/>
    <text x="255" y="60" font-size="9" fill="#475569" text-anchor="middle">compile</text>
    <rect x="290" y="38" width="180" height="60" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="380" y="58" font-size="10" font-weight="700" fill="#052e16" text-anchor="middle">HTML</text>
    <text x="380" y="78" font-size="9" fill="#052e16" text-anchor="middle">headings, paragraphs</text>

    <!-- MDX -->
    <text x="360" y="140" font-size="12" font-weight="700" fill="#1e1b4b" text-anchor="middle">MDX — prose + components</text>
    <rect x="40" y="156" width="180" height="70" rx="8" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="130" y="176" font-size="10" font-family="ui-monospace, monospace" fill="#1e1b4b" text-anchor="middle">post.mdx</text>
    <text x="130" y="194" font-size="9" font-family="ui-monospace, monospace" fill="#1e1b4b" text-anchor="middle">prose +</text>
    <text x="130" y="210" font-size="9" font-family="ui-monospace, monospace" fill="#1e1b4b" text-anchor="middle">&lt;Chart data={...} /&gt;</text>
    <line x1="220" y1="191" x2="290" y2="191" stroke="#64748b" stroke-width="1.5" marker-end="url(#mdxarrow)"/>
    <text x="255" y="183" font-size="9" fill="#475569" text-anchor="middle">compile</text>
    <rect x="290" y="156" width="180" height="70" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="380" y="178" font-size="10" font-weight="700" fill="#052e16" text-anchor="middle">HTML + component</text>
    <text x="380" y="196" font-size="9" fill="#052e16" text-anchor="middle">prose renders,</text>
    <text x="380" y="212" font-size="9" fill="#052e16" text-anchor="middle">Chart embedded in place</text>

    <text x="600" y="194" font-size="9" font-style="italic" fill="#1e1b4b" text-anchor="middle">interactive piece</text>
    <text x="600" y="208" font-size="9" font-style="italic" fill="#1e1b4b" text-anchor="middle">inside the prose</text>
  </g>
</svg>
```

## Local vs remote MDX

Two source patterns:

- **Local MDX** — .mdx files inside the project, imported as components or rendered via the file-system router. Best when content is versioned with the code.
- **Remote MDX** — MDX fetched dynamically on the server (from a CMS, a database, an API) and compiled at runtime or build. Best when content is authored outside the codebase.

The roadmap is explicit that Next.js supports both, including the use of MDX in Server Components — the App Router's default [1].

## How I use this

For anything prose-heavy with occasional interactivity — a blog, docs with embedded demos — MDX is the natural fit. I keep long-form content in .mdx files, drop in React components where the prose needs them (a chart, a callout, an interactive example), and let the build transform it. For pure-prose pages with zero interactivity, plain Markdown suffices. For content owned by non-developers (a CMS), I fetch it remotely and compile MDX server-side. The principle: match the source to who authors the content, and reach for MDX when prose and components need to coexist.

## References

[1] Vercel, "How to use markdown and MDX in Next.js," Next.js Docs, 2024. [Online]. Available: [https://nextjs.org/docs/app/guides/mdx](https://nextjs.org/docs/app/guides/mdx)

[2] MDX, "MDX — Markdown for the component era," mdxjs.com, 2024. [Online]. Available: [https://mdxjs.com/](https://mdxjs.com/)

[3] "Markdown — getting started," markdownguide.org, 2024. [Online]. Available: [https://www.markdownguide.org/](https://www.markdownguide.org/)

```quiz
Q: What's the relationship between Markdown and MDX?
- MDX is a faster Markdown parser
- MDX is a superset of Markdown that lets you write JSX (React components) directly inside the markdown
correct: 1
explain: MDX extends Markdown with JSX, so prose and interactive components can coexist in the same file.

Q: Does MDX work in Server Components in the App Router?
- No — it requires 'use client'
- Yes — the Next.js plugin transforms MDX at build time so it renders server-side by default
correct: 1
explain: The App Router handles the MDX transform so it works in Server Components by default, no 'use client' required.

Q: What distinguishes local MDX from remote MDX?
- Local is faster; remote is more secure
- Local MDX lives as .mdx files in the project; remote MDX is fetched dynamically (from a CMS/API) and compiled server-side
correct: 1
explain: The source differs: local files versioned with the code versus remote content authored outside the codebase. The choice depends on who authors the content.

Q: A good use case for MDX is…
- a static landing page with no interactivity
- a blog post that needs an embedded interactive chart alongside the prose
correct: 1
explain: MDX's strength is mixing prose and components. A blog post with an embedded chart, demo, or widget is exactly the case it was built for.
```
