Getting Started with MDX

August 28, 2019

I previously blogged about an issue I dealt with while converting my Jekyll blog to Gatsby. I wanted to include a script for a D3.js chart, and my workaround was to inject the script using componentDidMount in the blog post template. While this solution did work, it felt less than ideal. Specifying the script in the frontmatter and then verifying it in the template component was awkward in practice — something that would be a pain to do on a regular basis and therefore impede the expressiveness and fluidity that make React and JavaScript fun.

I wanted a simple, declarative way to embed an interactive JavaScript component in my markdown, much like Jekyll had allowed me to include a D3.js chart by simply adding a couple script tags. I didn’t know it before, but a great way to do that in Gatsby is MDX.

MDX allows you to import code and put JSX right into your markdown, making it feel more natural to include interactive components in blog posts. There’s a Gatsby plugin that makes it easy to get started if you’re using Gatsby, and there are several other ways to use MDX as well. Once configured, using MDX is simple:

Click to change color

import RandomColor from "./random-color"

<RandomColor />

gatsby-plugin-mdx can process regular markdown .md files as well as .mdx files, so my first thought was to have it replace my preexisting markdown processor. I ran into some issues with inline style tags from old posts though, so I decided to keep gatsby-transformer-remark as my standard markdown processor and only use gatsby-plugin-mdx to process .mdx files.

GraphQL Schema Customization

To do this, I configured GraphQL to handle both node types from the two separate plugins by creating an interface, which returns an array generated from both types in GraphQL queries:

// gatsby-node.js
exports.createSchemaCustomization = ({ actions }) => {
  const { createTypes } = actions
  const typeDefs = `
    interface Markdown @nodeInterface {
      id: ID!
      fields: Fields
      frontmatter: Frontmatter
      excerpt: String
    }

    type Fields {
      slug: String!
    }

    type Frontmatter {
      title: String
      date: Date @dateformat
    }

    type MarkdownRemark implements Node & Markdown {
      id: ID!
      fields: Fields
      frontmatter: Frontmatter
    }

    type Mdx implements Node & Markdown {
      id: ID!
      fields: Fields
      frontmatter: Frontmatter
    }
  `
  createTypes(typeDefs)
}

This makes it possible to just query generic Markdown nodes instead of having to deal with MarkdownRemark and Mdx nodes separately, while still being able to filter and sort the results even though they’re generated from different types of nodes. It’s also possible to query fields that are not common to both types of nodes using inline fragments:

query BlogPostBySlug($slug: String!) {
  markdown(fields: {slug: {eq: $slug}}) {
    frontmatter {
      title
      date(formatString: "MMMM DD, YYYY")
    }
    excerpt
    ... on Mdx {
      body
    }
    ... on MarkdownRemark {
      html
    }
  }
}

I found this setup made it easier to get started with MDX, already having some posts written with gatsby-transformer-remark, and it also allows integrating other markdown processors in the future. MDX makes it simple to include JSX components in markdown and author content in way that will feel very familiar to anyone used to working with React. It can be a bit more work than putting together a basic markdown blog, but it is as they say for ambitious projects.