How to add MDX to an Existing Gatsby Site

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:

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, this guide will show you a step-by-step walk-through for adding it to your website. You can poke around with the finished code for this tutorial in this sandbox, or check out the GitHub repo.

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.

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:

{
-     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:

{
      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.

- 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:

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:

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:

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

The new query becomes:

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

Now edit the following line:

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

Then, in the onCreateNode export:

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:

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.

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

The same needs to be done in the GraphQL query.

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

After the change, the query becomes:

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:

- 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.

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:

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:

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.

- <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. Import components right in your mdx file or write some JSX:

---
title: "Hello MDX!"
date: "2021-10-25"
description: "The first post using MDX!"
---
import {Button} from './button.js'

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 onClick={alert("Hello MDX!")}>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:

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 other branches in the GitHub repo for updates to the tutorial project.

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

This article was originally published on my website.

Cheers!

37