Moving to MDX

9th March 2020

MDX if you haven't heard of it is an amazing merging between markdown and jsx. It allows you to import and use React components in your markdown, this makes for some amazing posts like this one by Kent C. Dodds.

I want to add it here so I was going to write up how to add MDX to a Gatsby site that already has a wide range of posts and content.

To start with lets install MDX.

1npm install --save gatsby-plugin-mdx @mdx-js/mdx @mdx-js/react
Language bash

So I already have a loads of Gatsby Remark plugins configured for my images and code blocks which I'd like to keep. Thankfully gatsby-plugin-mdx already supports all gatsby-transformer-remark plugins out of the box.

At first I thought about running both mdx and markdown side by side but this idea was going to take far more work than it would to simply swap everything to MDX. To that end I removed gatsby-transformer-remark from my config and renamed every .md file to .mdx a task made trivial with power rename.

It's now a case of changing every allMarkdownRemark and markdownRemark query to allMdx and mdx respectively. I also need to replace the html in my queries with body to get the mdx render function.

I used HTMR to enchance my markdown and this is no longer needed as the MDX rendering components can do the same job. My Content component now looks like this:

1import React, {ReactHTMLElement} from 'react'
2import {OutboundLink} from 'gatsby-plugin-google-gtag'
3import {Link} from 'gatsby'
4import {MDXProvider} from "@mdx-js/react"
5import {MDXRenderer} from 'gatsby-plugin-mdx'
7const Anchor = (props: ReactHTMLElement<HTMLAnchorElement>["props"]) => {
8 const {href} = props
10 if(href!.substr(0, 4) === 'http'){
11 return <OutboundLink href={href!}>{props.children}</OutboundLink>
12 }
14 return <Link to={href!}>{props.children}</Link>
17const Paragraph = (node: Partial<ReactHTMLElement<HTMLParagraphElement>["props"]>) => {
18 let className = ''
20 if(
21 node.children
22 &&
23 (
24 (
25 (node.children as any)[0]
26 &&
27 (node.children as any)[0].props
28 &&
29 (node.children as any)[0].props.className === 'gatsby-resp-image-wrapper'
30 )
31 ||
32 (
33 (node.children as any).props
34 &&
35 (node.children as any).props.className === 'gatsby-resp-image-wrapper'
36 )
37 )
38 ){
39 className = "full-width"
40 }
42 return <p {...node} className={className} />
45export const Content: React.FC<{mdx: string}> = ({mdx}) => {
46 return <MDXProvider
47 components={{
48 a: Anchor,
49 p: Paragraph
50 }}
51 >
52 <MDXRenderer>{mdx}</MDXRenderer>
53 </MDXProvider>
Language ts

Which is pretty similar to the old system, the paragraph tag modifications are from when I moved over to a css grid layout. A slight tweak to each page to rename the html to mdx on the Content component.

So now I can import React components and use them in my posts. As a simple example here is a counter component. It's using useState to keep track of the count and prooves just how much power I have now with MDX.

Count: 0

I did fall into a trap that Gatsby needs to be restarted for MDX to import files, I spent an embarrasingly long time trying to work out why I was getting errors.

You can view the full commit on GitHub which shows all the work that went into moving over to MDX. Most of the commit is renaming *.md to *.mdx but the tsx and ts file changes will be of interest.

I have some ideas for the new features that MDX will let me use and you will be seeing some posts shortly that use them.