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:
- Next.js: 14.2.4
- next-mdx-remote: 5.0.0
- prism-react-renderer: 2.3.1
- React: 18.3.1
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'34type Props = {5 children?: ReactNode6}78export const HighlightedCode = ({ children }: Props) => {9 if (10 !isValidElement(children) ||11 typeof children.props.children !== 'string'12 ) {13 return null14 }1516 const code = children.props.children17 const language = children.props.className?.replace('language-', '').trim()1819 if (!code || !language) return null2021 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 null3}
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<MDXRemote2 {...source}3 components={{4 pre: HighlightedCode,5 }}6/>
Final Notes
This setup is simple, fast, and works well with MDX + TypeScript