DEV Community

Cover image for How to add MDX to an Existing Gatsby Site
Anna Rossetti
Anna Rossetti

Posted on • Originally published at annarossetti.com

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:

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, 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
Enter fullscreen mode Exit fullscreen mode

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`,
        ],
      },
    },
Enter fullscreen mode Exit fullscreen mode

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`,
        ],
      },
    },
Enter fullscreen mode Exit fullscreen mode

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`,
Enter fullscreen mode Exit fullscreen mode

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",
          },
        ],
      },
    },
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
            }
          }
        }
      }
    `
  )
Enter fullscreen mode Exit fullscreen mode

The new query becomes:

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

Now edit the following line:

-  const posts = result.data.allMarkdownRemark.nodes
+  const posts = result.data.allMdx.nodes
Enter fullscreen mode Exit fullscreen mode

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,
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

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,
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The same needs to be done in the GraphQL query.

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

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
        }
      }
    }
  }
`
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
      }
    }
  }
`
Enter fullscreen mode Exit fullscreen mode

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
      }
    }
  }
`
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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/>
Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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

Discussion (0)