Why I Love MDX in Next.js
When I started building my portfolio, I wanted a way to write content that was both easy to manage and powerful enough to include interactive components. MDX turned out to be the perfect solution, combining the simplicity of Markdown with the power of React components.
Getting Started with MDX
First, let's set up MDX in your Next.js project. You'll need to install the necessary dependencies:
npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx
Then, configure your next.config.js
:
const withMDX = require('@next/mdx')({
extension: /\.mdx?$/,
options: {
// If you use remark-gfm, you'll need to use next.config.mjs
// as the package is ESM only
remarkPlugins: [],
rehypePlugins: []
}
})
/** @type {import('next').NextConfig} */
const nextConfig = {
// Configure pageExtensions to include md and mdx
pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx']
}
// Merge MDX config with Next.js config
module.exports = withMDX(nextConfig)
Creating Your First MDX Page
Here's a simple MDX page that showcases basic features:
---
title: 'My First MDX Post'
date: '2024-12-12'
---
import { CustomAlert } from '../components/CustomAlert'
# Welcome to My MDX Post
This is a paragraph with **bold** and _italic_ text.
<CustomAlert type='info'>
This is a custom React component inside MDX!
</CustomAlert>
## Code Example
```javascript
const greeting = 'Hello, MDX!'
console.log(greeting)
```
Let me explain why this is cool...
## Setting Up the MDX Layout
I like to create a layout component specifically for MDX content:
```tsx
// components/MDXLayout.tsx
import { ReactNode } from 'react'
interface MDXLayoutProps {
children: ReactNode
frontmatter: {
title: string
date: string
}
}
export default function MDXLayout({ children, frontmatter }: MDXLayoutProps) {
return (
<article className="prose lg:prose-xl dark:prose-invert mx-auto">
<h1>{frontmatter.title}</h1>
<time dateTime={frontmatter.date}>
{new Date(frontmatter.date).toLocaleDateString()}
</time>
{children}
</article>
)
}
Working with Frontmatter
Frontmatter is super useful for storing metadata. Here's how I handle it:
// lib/mdx.ts
import path from 'path'
import fs from 'fs'
import matter from 'gray-matter'
export interface Frontmatter {
title: string
date: string
summary?: string
tags?: string[]
}
export async function getMDXContent(slug: string) {
const filePath = path.join(process.cwd(), 'content', `${slug}.mdx`)
const fileContent = fs.readFileSync(filePath, 'utf8')
const { data, content } = matter(fileContent)
return {
frontmatter: data as Frontmatter,
content
}
}
Adding Custom Components
One of my favorite features is the ability to use React components in MDX:
// components/CodeBlock.tsx
interface CodeBlockProps {
children: string
language: string
}
export function CodeBlock({ children, language }: CodeBlockProps) {
return (
<div className='relative'>
<pre className={`language-${language}`}>
<code>{children}</code>
</pre>
<button
onClick={()=> navigator.clipboard.writeText(children)}
className='absolute right-2 top-2'
>
Copy
</button>
</div>
)
}
Advanced MDX Features
Custom Remark/Rehype Plugins
I use several plugins to enhance my MDX content:
// next.config.js
const withMDX = require('@next/mdx')({
options: {
remarkPlugins: [require('remark-gfm'), require('remark-prism')],
rehypePlugins: [require('rehype-slug'), require('rehype-autolink-headings')]
}
})
Dynamic Imports in MDX
You can dynamically import components in your MDX files:
import dynamic from 'next/dynamic'
export const Chart = dynamic(() => import('../components/Chart'))
# Sales Report
Here's our quarterly performance:
<Chart data={salesData} />
Best Practices I've Learned
- Organize Content: Keep your MDX files in a structured directory:
content/
blog/
post-1.mdx
post-2.mdx
projects/
project-1.mdx
- Type Safety: Use TypeScript for better component integration:
// types/mdx.d.ts
declare module '*.mdx' {
import { ReactNode } from 'react'
export const frontmatter: {
title: string
date: string
[key: string]: any
}
export default function MDXContent(props: {
children: ReactNode
}): JSX.Element
}
- Custom Components Map: Create a components map for consistent styling:
const components = {
h1: props => <h1 className="text-3xl font-bold" {...props} />,
h2: props => <h2 className="text-2xl font-semibold" {...props} />,
code: CodeBlock,
// Add more component overrides
}
Conclusion
MDX has transformed how I write content for my Next.js applications. It combines the best of Markdown's simplicity with React's component-based power. Whether you're building a blog, documentation site, or portfolio, MDX provides the flexibility and features you need.
Resources
Happy coding! Feel free to reach out if you have any questions about using MDX in your Next.js projects.