
Setting up MDX in Next.js with Code Highlighting
Setting up MDX in Next.js with Code Highlighting
MDX is a powerful format that lets you write JSX in your markdown content. When combined with Next.js, it creates a fantastic blogging platform. In this post, I'll show you how to set up MDX in Next.js with syntax highlighting, line numbers, and code block titles.
Prerequisites
You'll need a Next.js project with TypeScript. If you don't have one, create it:
npx create-next-app@latest my-blog --typescript1. Installing Dependencies
First, install the necessary packages:
yarn add @mdx-js/loader @mdx-js/react @next/mdx rehype-autolink-headings rehype-code-titles rehype-pretty-code rehype-slug remark-gfm remark-frontmatter remark-mdx-frontmatter shikiLet's break down what each package does:
@mdx-js/loader&@mdx-js/react: Core MDX functionality@next/mdx: Next.js integration for MDXrehype-autolink-headings: Adds anchor links to headingsrehype-code-titles: Enables code block titlesrehype-pretty-code: Syntax highlighting powered by Shikirehype-slug: Adds IDs to headingsremark-gfm: GitHub Flavored Markdown supportremark-frontmatter&remark-mdx-frontmatter: YAML frontmatter supportshiki: Syntax highlighter
2. Configuring Next.js
Create or update your next.config.ts:
import type { NextConfig } from 'next'
import createMDX from '@next/mdx'
import remarkGfm from 'remark-gfm'
import remarkFrontmatter from 'remark-frontmatter'
import rehypeSlug from 'rehype-slug'
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
import rehypeCodeTitles from 'rehype-code-titles'
import rehypePrettyCode from 'rehype-pretty-code'
const prettyCodeOptions = {
filterMetaString: (string: string) => string.replace(/\{.*\}/, ''),
keepBackground: true,
}
const withMDX = createMDX({
options: {
rehypePlugins: [
rehypeSlug,
rehypeAutolinkHeadings,
[rehypePrettyCode, prettyCodeOptions],
rehypeCodeTitles,
],
remarkPlugins: [remarkGfm, remarkFrontmatter],
},
})
const nextConfig: NextConfig = {
pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
// ... other config options
}
export default withMDX(nextConfig)3. Setting up MDX Components
Create an mdx-components.tsx file to customize how MDX elements are rendered:
'use client'
import React from 'react'
import type { MDXComponents } from 'mdx/types'
import Link from 'next/link'
import { ExternalLink } from '@/components/ExternalLink'
import { CodeBlock } from '@/components/CodeBlock'
const components: MDXComponents = {
a: ({ href, children }) => {
if (!href) return <>{children}</>
const isExternal = href.startsWith('http')
if (isExternal) {
return <ExternalLink href={href}>{children}</ExternalLink>
}
return <Link href={href}>{children}</Link>
},
code: ({ children, className }) => {
// Extract language and filename from className (e.g., "language-typescript:file.ts")
const [lang, fileWithHighlights] = (className || '')
.replace('language-', '')
.split(':')
// Extract highlights from the trailing {1,2-3}
const match = fileWithHighlights?.match(/(.*?)\s*{(.+)}$/)
const highlights = match?.[2] || ''
const cleanFile = match?.[1]?.trim() || fileWithHighlights || ''
return (
<CodeBlock
file={cleanFile}
highlights={highlights}
language={lang}
mdxType="code"
>
{children}
</CodeBlock>
)
},
}
export function useMDXComponents(): MDXComponents {
return components
}4. Creating the CodeBlock Component
First, let's style inline code blocks. Add these styles to your MDX wrapper or global styles:
const MDXStyles = styled.div`
code:not(pre code) {
background: ${props => props.theme.colors.codeBackground || '#2d2d2d'};
border-radius: 4px;
color: ${props => props.theme.colors.code || '#e6e6e6'};
font-family: monospace;
font-size: 0.9em;
padding: 0.2em 0.4em;
}
`
export function MDXWrapper({ children }: { children: React.ReactNode }) {
const components = useMDXComponents()
return (
<MDXProvider components={components}>
<MDXStyles>{children}</MDXStyles>
</MDXProvider>
)
}This will style inline-code elements with:
- A darker background
- Rounded corners
- Monospace font
- Slight padding
- Proper color contrast
Now create a CodeBlock component to handle syntax highlighting:
'use client'
import styled from 'styled-components'
interface CodeBlockProps {
children: React.ReactNode
file?: string
highlights?: string
language?: string
mdxType?: string
}
const Pre = styled.pre`
border-radius: 8px;
font-size: 14px;
margin: 20px 0;
overflow-x: auto;
padding: 16px;
position: relative;
&:hover button {
opacity: 1;
}
`
const CodeTitle = styled.div`
background: #1e1e1e;
border-radius: 8px 8px 0 0;
color: #fff;
font-family: monospace;
font-size: 14px;
padding: 8px 16px;
`
export function CodeBlock({
children,
file,
highlights,
language,
mdxType,
}: CodeBlockProps) {
if (mdxType !== 'code') return <>{children}</>
return (
<div>
{file && <CodeTitle>{file}</CodeTitle>}
<Pre
data-line-numbers
data-highlights={highlights}
data-language={language}
>
{children}
</Pre>
</div>
)
}5. Creating an MDX Wrapper
Create an MDX wrapper component to provide the MDX context:
'use client'
import { MDXProvider } from '@mdx-js/react'
import { useMDXComponents } from '../mdx-components'
export function MDXWrapper({ children }: { children: React.ReactNode }) {
const components = useMDXComponents()
return <MDXProvider components={components}>{children}</MDXProvider>
}6. Using MDX in Your Pages
Now you can create MDX files in your pages or app directory. Here's an example of how to use it in the App Router:
import { MDXWrapper } from '@/components/MDXWrapper'
import { getMDXComponent } from 'next-contentlayer/mdx'
export default async function BlogPost({ params }: { params: { slug: string } }) {
// Your logic to fetch MDX content
const MDXContent = getMDXComponent(content)
return (
<MDXWrapper>
<MDXContent />
</MDXWrapper>
)
}Using Code Blocks
Now you can use code blocks in your MDX files with various features:
- Basic code block:
const hello = 'world'- With filename:
const hello = 'world'- With line highlighting:
const hello = 'world'
console.log(hello)
// This line is highlighted
// This line is highlighted
// This line is highlightedConclusion
You now have a fully functional MDX setup in your Next.js project with:
- Syntax highlighting
- Line numbers
- Code block titles
- Line highlighting
- GitHub-flavored markdown
- Automatic heading links
This setup provides a great foundation for a technical blog or documentation site. The syntax highlighting is powered by Shiki, which provides VS Code-quality highlighting, and the MDX integration allows you to use React components directly in your markdown files.
Happy coding!