Setting Up MDX for Content-Rich Blogging
How I set up MDX with Next.js to create a flexible blogging system that combines markdown simplicity with React component power.
Setting Up MDX for Content-Rich Blogging
When building this blog, I wanted the simplicity of Markdown with the power of React components. MDX (Markdown + JSX) provides exactly that - here's how I set it up.
Why MDX?
Traditional Markdown is great for simple content, but sometimes you need more:
- Interactive components within content
- Custom styling for specific elements
- Rich media embeds
- Complex layouts within posts
MDX lets you write Markdown while seamlessly embedding React components:
# Regular Markdown Heading
This is normal text with **bold** and *italic* formatting.
<CustomComponent data="some-value" />
Back to regular markdown!
The Technical Setup
My MDX setup uses several key packages:
{
"@next/mdx": "^15.4.6",
"@mdx-js/loader": "^3.1.0",
"@mdx-js/react": "^3.1.0",
"next-mdx-remote": "^5.0.0",
"gray-matter": "^4.0.3"
}
Configuration
The Next.js config enables MDX processing:
import createMDX from '@next/mdx';
const withMDX = createMDX({
options: {
remarkPlugins: [],
rehypePlugins: [],
},
});
export default withMDX(config);
File Structure
Posts live in src/content/posts/ as .mdx files:
src/content/posts/
├── welcome-to-my-blog.mdx
├── building-with-nextjs-15.mdx
└── mdx-blogging-setup.mdx
Each post starts with frontmatter:
---
title: "Post Title"
date: "2025-01-15"
excerpt: "Brief description"
readTime: "5 min read"
---
# Post content starts here
Content Processing
I use gray-matter to parse frontmatter and next-mdx-remote for server-side rendering:
export async function getBlogPost(slug: string) {
const fullPath = path.join(postsDirectory, `${slug}.mdx`);
const fileContents = fs.readFileSync(fullPath, 'utf8');
const { data, content } = matter(fileContents);
return {
slug,
title: data.title,
date: data.date,
excerpt: data.excerpt,
content, // Raw MDX content
};
}
Custom Components
The power of MDX shines with custom components. I created an MDX components file:
export function useMDXComponents(components: MDXComponents) {
return {
h1: ({ children }) => (
<h1 className="text-3xl font-bold mb-4 border-b-2 border-gray-900 pb-2">
{children}
</h1>
),
blockquote: ({ children }) => (
<blockquote className="border-l-4 border-gray-900 pl-6 my-6 italic">
{children}
</blockquote>
),
// ... more custom components
};
}
Rendering Posts
In the blog post page, I render MDX with custom components:
import { MDXRemote } from 'next-mdx-remote/rsc';
export default async function BlogPost({ params }) {
const post = await getBlogPost(params.slug);
const components = useMDXComponents({});
return (
<div className="prose">
<MDXRemote source={post.content} components={components} />
</div>
);
}
Benefits of This Approach
- Performance - Static generation of all posts
- Flexibility - Can embed any React component
- Developer Experience - Write in familiar Markdown
- SEO - Server-rendered content with proper metadata
- Maintainability - Clean separation of content and code
Future Enhancements
Some ideas I'm considering:
- Syntax highlighting for code blocks
- Reading time calculation
- Table of contents generation
- Search functionality
- Comment system
Conclusion
MDX strikes the perfect balance between simplicity and power. For developers who want more than basic Markdown but don't need a full CMS, it's an excellent choice.
The setup takes a bit of work upfront, but the authoring experience is smooth, and the flexibility is worth it.
Have you used MDX in your projects? What components have you found most useful to embed in your content?