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.
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
.
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 {titledescriptionsiteUrlsite_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+ bodyfields {slug}frontmatter {titledate}}}}`,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 {idfields {slug}}}}`)
The new query becomes:
const result = await graphql(`{allMdx(sort: { fields: [frontmatter___date], order: ASC }limit: 1000) {nodes {idfields {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 } = actionsif (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 {excerptfields {slug}frontmatter {date(formatString: "MMMM DD, YYYY")titledescription}}}}`
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 }) {idexcerpt(pruneLength: 160)- html+ bodyfrontmatter {titledate(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 }) {idexcerpt(pruneLength: 160)bodyfrontmatter {titledate(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:
---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!jshere'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:
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!