MDX — Embedding JSX Components in Markdown Content
In this tutorial, you will learn about MDX. We cover key concepts, practical examples, and best practices to help you master this topic.
MDX combines markdown and JSX, letting you embed React components directly in content files for rich interactive documentation.
What You'll Learn
By the end of this tutorial, you'll understand how MDX extends markdown with JSX, how to import and use components in MDX files, configure MDX in SSG frameworks, and create custom components for content.
Why It Matters
Traditional markdown is limited to static content. MDX breaks this barrier by allowing interactive React components inside your content. Documentation, tutorials, and blog posts become dynamic learning experiences.
Real-World Use
A component library's documentation uses MDX to embed live code editors, interactive examples, and prop tables directly in markdown files. Developers read the docs, see the component, and edit examples all on the same page.
MDX Processing
graph TD
A[.mdx File] --> B[MDX Parser]
B --> C[Abstract Syntax Tree]
C --> D[JSX Compilation]
D --> E[JavaScript Component]
E --> F[SSG Build Process]
F --> G[Static HTML
+ JS for interactive parts]
G --> H[Browser Rendering]
H --> I[Interactive content]
style B fill:#4a90d9,color:#fff
style D fill:#e67e22,color:#fff
style F fill:#27ae60,color:#fff
Basic MDX
# Getting Started with MDX
MDX lets you use JSX in your markdown content.
This is regular markdown with **formatting**.
## Using Components
Import React components directly in your MDX file:
import { Button, Alert } from '../components/ui';
import CodeEditor from '../components/CodeEditor';
<Alert type="info">
This alert component is rendered inside markdown content!
No special syntax needed — just use JSX.
</Alert>
## Interactive Example
<CodeEditor
initialCode={`function greet(name) {\n return `Hello ${name}!`;\n}`}
language="javascript"
/>
## Regular Markdown Still Works
- Lists work normally
- **Bold** and *italic* still work
- Code blocks still render
<Button onClick={() => alert('Clicked!')}>
Interactive Button
</Button>
## Passing Props
<Card
title="MDX Guide"
description="Embed components in content"
variant="outline"
icon="book"
/>
MDX Configuration in Next.js
// next.config.js — MDX configuration
const withMDX = require('@next/mdx')({
extension: /\.mdx?$/,
options: {
remarkPlugins: [],
rehypePlugins: [],
providerImportSource: '@mdx-js/react',
},
});
module.exports = withMDX({
pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
});
// pages/docs/guide.mdx — MDX page in Next.js
// This file becomes a route at /docs/guide
Custom MDX Components
// components/mdx/LiveEditor.js — Interactive code editor
import { useState } from 'react';
import { LiveProvider, LiveEditor, LivePreview, LiveError } from 'react-live';
export default function LiveCodeEditor({ code, scope = {} }) {
const [showEditor, setShowEditor] = useState(true);
return (
<div className="live-editor">
<LiveProvider code={code} scope={scope}>
<div className="preview">
<LivePreview />
</div>
{showEditor && (
<div className="editor">
<LiveEditor />
</div>
)}
<LiveError className="error" />
<button onClick={() => setShowEditor(!showEditor)}>
{showEditor ? 'Hide Editor' : 'Show Editor'}
</button>
</LiveProvider>
</div>
);
}
// Usage in MDX:
// import LiveEditor from '../components/mdx/LiveEditor';
//
// <LiveEditor code={`<button>Click me</button>`} />
// components/mdx/PropsTable.js — Component props documentation
import { useEffect, useState } from 'react';
export default function PropsTable({ component }) {
const [props, setProps] = useState([]);
useEffect(() => {
async function loadProps() {
const mod = await import(`../../components/${component}`);
const Component = mod.default;
const propTypes = Component.propTypes || {};
const defaultProps = Component.defaultProps || {};
const entries = Object.entries(propTypes).map(([name, prop]) => ({
name,
type: prop.type?.name || 'any',
required: prop.required || false,
default: defaultProps[name]?.toString() || '-',
description: prop.description || '',
}));
setProps(entries);
}
loadProps();
}, [component]);
return (
<table className="props-table">
<thead>
<tr>
<th>Prop</th>
<th>Type</th>
<th>Required</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{props.map(prop => (
<tr key={prop.name}>
<td><code>{prop.name}</code></td>
<td>{prop.type}</td>
<td>{prop.required ? 'Yes' : 'No'}</td>
<td>{prop.default}</td>
<td>{prop.description}</td>
</tr>
))}
</tbody>
</table>
);
}
// Usage in MDX:
// import PropsTable from '../components/mdx/PropsTable';
//
// ## Button Component Props
// <PropsTable component="Button" />
MDX with Gatsby
// gatsby-config.js — MDX with Gatsby
module.exports = {
plugins: [
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'pages',
path: `${__dirname}/src/pages`
}
},
{
resolve: 'gatsby-plugin-mdx',
options: {
extensions: ['.mdx', '.md'],
gatsbyRemarkPlugins: [
{
resolve: 'gatsby-remark-images',
options: { maxWidth: 800 }
}
],
mdxOptions: {
remarkPlugins: [],
rehypePlugins: [],
}
}
}
]
};
// src/pages/guide.mdx — MDX page in Gatsby
// Automatically becomes a page at /guide
Common Mistakes
- Not configuring MDX extensions. MDX files require specific file extensions (.mdx) and Webpack configuration. Default Next.js doesn't handle .mdx without @next/mdx.
- Trying to use hooks in MDX scope. Hooks like useState in MDX files don't work unless you wrap them in a component. Components manage their own state.
- Importing components with broken paths. Import paths in MDX are relative to the file. Use absolute imports with aliases to avoid confusion.
- Over-using custom components in content. Not every paragraph needs an interactive component. Reserve MDX for parts that genuinely benefit from interactivity.
- Forgetting to add remark/rehype plugins. Markdown features like footnotes, tables, and code highlighting need plugins. Configure them in your MDX setup.
Practice Questions
- How does MDX differ from standard markdown?
- How do you import and use React components in MDX files?
- What configuration is needed to use MDX in Next.js?
- Can you use hooks like useState directly in MDX files?
- What are remark and rehype plugins used for in MDX?
Challenge: Create an interactive MDX documentation page: build a live code editor component, a props table generator, and an interactive example. Use all three in a single MDX file that documents a custom Button component.
FAQ
Mini Project
Create an interactive component documentation site: build 3 custom components (Button, Card, Alert) with prop types, create MDX documentation pages for each with live code editors, props tables, and interactive examples, and configure MDX in Next.js.
What's Next
Now learn how to combine SSG with Headless CMS + SSG for team-friendly content management workflows.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro