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.