How to add MDX to an Existing Gatsby Site

October 25, 2021

My previous article featured a guide for setting up a personal website using Gatsby. One of the first items on my upgrade list for this type of project is adding support for MDX, which allows the use of React components inside Markdown files.

Code snippets are really important for a developer’s blog, so I like to use a custom code component to display them. I love the look and functionality of code blocks on Gatsby’s official docs.

A screenshot of GatsbyJS's online documentation showing the custom code fragment

There are loads of other neat things that you can do with MDX, like Josh Comeau’s custom text emphasis using animations.

If you started your Gatsby project without MDX, here’s a step-by-step walk-through for adding it to your website:

Step 1: Install the MDX Packages & Official MDX Plugins

To get started, you need to install the @mdx-js/mdx and @mdx-js/react packages as well as Gatsby’s official gatsby-plugin-mdx and gatsby-plugin-feed-mdx.

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

Step 2: Edit the Gatsby Configuration File

In gatsby-config.js, edit the configuration for the gatsby-transformer-remark plugin by replacing it with gatsby-plugin-mdx:

diff
{
- resolve: `gatsby-transformer-remark`,
+ resolve: `gatsby-plugin-mdx`,
options: {
+ extensions: [`.mdx`, `.md`],
- plugins: [
gatsbyRemarkPlugins: [ //added
{
resolve: `gatsby-remark-images`,
options: {
maxWidth: 630,
},
},
{
resolve: `gatsby-remark-responsive-iframe`,
options: {
wrapperStyle: `margin-bottom: 1.0725rem`,
},
},
`gatsby-remark-prismjs`,
`gatsby-remark-copy-linked-files`,
`gatsby-remark-smartypants`,
],
},
},

It should now look like this:

jsx
{
resolve: `gatsby-plugin-mdx`,
options: {
extensions: [`.mdx`, `.md`],
gatsbyRemarkPlugins: [
{
resolve: `gatsby-remark-images`,
options: {
maxWidth: 630,
},
},
{
resolve: `gatsby-remark-responsive-iframe`,
options: {
wrapperStyle: `margin-bottom: 1.0725rem`,
},
},
`gatsby-remark-prismjs`,
`gatsby-remark-copy-linked-files`,
`gatsby-remark-smartypants`,
],
},
},

In the same gatsby-config.js file, replace gatsby-plugin-feed with gatsby-plugin-feed-mdx.

diff
- resolve: `gatsby-plugin-feed`,
+ resolve: `gatsby-plugin-feed-mdx`,

Then, change the plugin’s configuration to replace all occurrences of allMarkdownRemark with allMDX and replace html with body in the GraphQL query:

diff
resolve: `gatsby-plugin-feed-mdx`,
options: {
query: `
{
site {
siteMetadata {
title
description
siteUrl
site_url: siteUrl
}
}
}
`,
feeds: [
{
- serialize: ({ query: { site, allMarkdownRemark } }) => {
+ serialize: ({ query: { site, allMdx } }) => {
- return allMarkdownRemark.nodes.map(node => {
+ return allMdx.nodes.map(node => {
return Object.assign({}, node.frontmatter, {
description: node.excerpt,
date: node.frontmatter.date,
url: site.siteMetadata.siteUrl + node.fields.slug,
guid: site.siteMetadata.siteUrl + node.fields.slug,
custom_elements: [{ "content:encoded": node.html }],
})
})
},
query: `
{
- allMarkdownRemark(
+ allMdx(
sort: { order: DESC, fields: [frontmatter___date] },
) {
nodes {
excerpt
- html
+ body
fields {
slug
}
frontmatter {
title
date
}
}
}
}
`,
output: "/rss.xml",
title: "Jane Doe RSS Feed",
},
],
},
},

Step 3: Uninstall Redundant Plugins

Now that gatsby-transformer-remark and gatsby-plugin-feed are no longer used, you can uninstall them by running the following commands in the terminal:

bash
npm uninstall --save gatsby-transformer-remark gatsby-plugin-feed

Remember to save the changes in gatsby-config.js.

Step 4: Edit the Gatsby Node File

In the gatsby-node.js file, start by updating the GraphQL query:

diff
const result = await graphql(
`
{
- allMarkdownRemark(
+ allMdx(
sort: { fields: [frontmatter___date], order: ASC }
limit: 1000
) {
nodes {
id
fields {
slug
}
}
}
}
`
)

The new query becomes:

js
const result = await graphql(
`
{
allMdx(
sort: { fields: [frontmatter___date], order: ASC }
limit: 1000
) {
nodes {
id
fields {
slug
}
}
}
}
`
)

Now edit the following line:

diff
- const posts = result.data.allMarkdownRemark.nodes
+ const posts = result.data.allMdx.nodes

Then, in the onCreateNode export:

diff
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
- if (node.internal.type === `MarkdownRemark`) {
+ if (node.internal.type === `Mdx`) {
const value = createFilePath({ node, getNode })
createNodeField({
name: `slug`,
node,
value,
})
}
}

After changes, it becomes:

jsx
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `Mdx`) {
const value = createFilePath({ node, getNode })
createNodeField({
name: `slug`,
node,
value,
})
}
}

Remember to save the changes in gatsby-node.js.

Step 5: Edit the Front Page

In src/pages/index.js , replace occurrences of allMarkdownRemark with allMdx in the BlogIndex functional component.

diff
- const posts = data.allMarkdownRemark.nodes
+ const posts = data.allMdx.nodes

The same needs to be done in the GraphQL query.

diff
- allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
+ allMdx(sort: { fields: [frontmatter___date], order: DESC }) {

After the change, the query becomes:

jsx
export const pageQuery = graphql`
query {
site {
siteMetadata {
title
}
}
allMdx(sort: { fields: [frontmatter___date], order: DESC }) {
nodes {
excerpt
fields {
slug
}
frontmatter {
date(formatString: "MMMM DD, YYYY")
title
description
}
}
}
}
`

Remember to save the changes in src/pages/index.js.

Step 6: Edit the Blog Post Template File

In src/templates/blog-post.js, replace markdownRemark with mdx in the BlogPostTemplate functional component:

diff
- const post = data.markdownRemark
+ const post = data.mdx

Also replace occurrences of markdownRemark with mdx in the GraphQL query, and use body instead of html.

diff
export const pageQuery = graphql`
query BlogPostBySlug(
$id: String!
$previousPostId: String
$nextPostId: String
) {
site {
siteMetadata {
title
}
}
- markdownRemark(id: { eq: $id }) {
+ mdx(id: { eq: $id }) {
id
excerpt(pruneLength: 160)
- html
+ body
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
description
}
}
- previous: markdownRemark(id: { eq: $previousPostId }) {
+ previous: mdx(id: { eq: $previousPostId }) {
fields {
slug
}
frontmatter {
title
}
}
- next: markdownRemark(id: { eq: $nextPostId }) {
+ next: mdx(id: { eq: $nextPostId }) {
fields {
slug
}
frontmatter {
title
}
}
}
`

The final query looks like this:

jsx
export const pageQuery = graphql`
query BlogPostBySlug(
$id: String!
$previousPostId: String
$nextPostId: String
) {
site {
siteMetadata {
title
}
}
mdx(id: { eq: $id }) {
id
excerpt(pruneLength: 160)
body
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
description
}
}
previous: mdx(id: { eq: $previousPostId }) {
fields {
slug
}
frontmatter {
title
}
}
next: mdx(id: { eq: $nextPostId }) {
fields {
slug
}
frontmatter {
title
}
}
}
`

Next, add an import statement for MDXRenderer at the top of the file:

diff
import * as React from "react"
import { Link, graphql } from "gatsby"
+ import { MDXRenderer } from "gatsby-plugin-mdx"

Next, find the the <section/> element with the dangerouslySetInnerHTML attribute and replace it with the MDXRenderer component.

diff
- <section dangerouslySetInnerHTML={{ __html: post.html }}
- itemProp="articleBody"
- />
+ <MDXRenderer>{post.body}<MDXRenderer/>

Remember to save the changes in src/templates/blog-post.js.

Step 7: Add an .mdx Blog Post to Test Your Changes

With all setup now complete, it’s time to test that everything is working as it should. Add a new index.mdx file in content/blog/hello-mdx:

markdown
---
title: Hello MDX!
date: "2021-10-25"
description: "The first post using MDX!"
---
This post is written in MDX, allowing you to embed a component after block of code which explains its creation!
js
here's a button in React!
<button>test</button>
Wow! Such button!
<button>test</button>

Now, run gatsby develop in your terminal and check out your new post. The <button> component should be rendered as an element:

Screenshot of the new blog post demonstrating successful MDX integration, with the alert box showing up after pressing the button component.

Finally, to make sure your RSS feed is being correctly generated, use gatsby build and gatsby serve, then navigate to localhost:9000/rss.xml. The RSS plugin does not generate a file in development mode, so you need to use a production build instead to test the functionality.

Finished!

And you’re done! If anything hasn’t quite gone according to plan, check out the official docs for the gatsby-plugin-mdx plugin, and the gatsby-plugin-feed-mdx plugin. If you’re new to MDX, Gatsby have an awesome guide for new users.

Also, if you are following along with my series on building up a personal website, you can check the GitHub repo for this update to the tutorial project.

And if you get stuck, you can always hit me up on Twitter for help!

Cheers!


Profile picture

Written by Anna Rossetti, UK based software engineer working on building delightful digital experiences for the web & mobile. Want to get in touch? Reach out on Twitter or by email.

© 2022 Anna Rossetti. All Rights Reserved. Handcrafted with ❤️ in England, UK 🇬🇧.