Back to posts
Using MDX with Next.js

Using MDX with Next.js

A comprehensive guide to integrating and using MDX in Next.js applications

Chernet Asmamaw

December 12, 2024

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

  1. Organize Content: Keep your MDX files in a structured directory:
content/
  blog/
    post-1.mdx
    post-2.mdx
  projects/
    project-1.mdx
  1. 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
}
  1. 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.