Logo

Adding Syntax Highlighting to MDX with Prism and Next.js

prismmdx

Overview

Code is easier to read and more fun to look at when it's highlighted properly. This is how I set up prism-react-renderer with MDX in a Next.js project.

Dependencies

Versions used when this was written:

Install

1npm install --save prism-react-renderer

Highlight Component

Created a component to render highlighted code blocks from MDX:

1import { ReactNode, isValidElement } from 'react'
2import { Highlight } from 'prism-react-renderer'
3
4type Props = {
5 children?: ReactNode
6}
7
8export const HighlightedCode = ({ children }: Props) => {
9 if (
10 !isValidElement(children) ||
11 typeof children.props.children !== 'string'
12 ) {
13 return null
14 }
15
16 const code = children.props.children
17 const language = children.props.className?.replace('language-', '').trim()
18
19 if (!code || !language) return null
20
21 return (
22 <Highlight code={code} language={language}>
23 {({ tokens, getLineProps, getTokenProps }) => (
24 <pre>
25 {tokens.map((line, index) => (
26 <div key={`token-${index}`} {...getLineProps({ line })}>
27 {line.map((token, index) => (
28 <span key={`line-${index}`} {...getTokenProps({ token })} />
29 ))}
30 </div>
31 ))}
32 </pre>
33 )}
34 </Highlight>
35 )
36}

Why ReactNode?

MDX components pass in ReactNode, not JSX.Element, so had to guard for invalid inputs like this:

1if (!isValidElement(children) || typeof children.props.children !== 'string') {
2 return null
3}

Breaking Changes in Prism v2

Ran into this:

1Module '"prism-react-renderer"' has no exported member 'defaultProps'

That’s expected. defaultProps is gone in v2. It’s handled internally now.

MDX Integration

Used it like this with next-mdx-remote:

1<MDXRemote
2 {...source}
3 components={{
4 pre: HighlightedCode,
5 }}
6/>

Final Notes

This setup is simple, fast, and works well with MDX + TypeScript