Intro to MDX in Gatsby

Daniel Stout

Most of you have probably used Markdown files in your Gatsby.js sites, and you already know it’s an amazing way to write content. But plain Markdown is geared towards text-based content, and it can be limiting when you want to step outside of that use case. That all changes with MDX, a superset of Markdown that allows us to embed JSX directly into Markdown files. Sounds awesome, doesn’t it? In this article we’ll explore the basics of MDX with Gatsby, including some introductory techniques to help you start using it right away.

Before we dive in, you will need to have a Gatsby project that is set up and ready to edit. If you need help getting to that point, please follow the steps in Your First Steps with Gatsby v2 and then return here afterwards.

Recommended React and GraphQL course

Installation

Thanks to Gatsby’s incredible plugins library, the installation process is easy! Using MDX with Gatsby only requires a single plugin, gatsby-plugin-mdx, along with the MDX peer dependencies.

Let’s install those now, like this:

$ yarn add gatsby-plugin-mdx @mdx-js/mdx @mdx-js/react

Let’s also install gatsby-source-filesystem so that we can make use of frontmatter, generate Gatsby nodes from local files, and use import/export functionality in our MDX files:

$ yarn add gatsby-source-filesystem 

While not technically required, this step is highly recommended — as it really opens the full potential of MDX content with Gatsby!

Configuration

Like with all Gatsby plugins, we need to add configuration details to the plugins section of gatsby-config.js.

Let’s configure both gatsby-plugin-mdx and gatsby-source-filesystem like this:

gatsby-config.js

module.exports = {
  //...siteMetadata, etc
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `pages`,
        path: `${__dirname}/src/pages/`,
      },
    },
    {
      resolve: `gatsby-plugin-mdx`,
      options: {
        defaultLayouts: {
          default: require.resolve(`./src/components/layout.js`),
      },
    },
    // ... other plugins
  ],
}

Notice that we are setting a default key in the defaultLayouts option. This will automatically wrap all MDX files with our site’s default layout.js component.

The gatsby-config.js file has been edited, so don't forget to restart the development environment before proceeding!

Configuration options

There are several configuration options available for gatsby-plugin-mdx:

  • extensions: (Array of strings) Sets file extensions that will be processed as MDX. I typically set this to ['.mdx', '.md'] to also process normal Markdown files as MDX.
  • defaultLayouts: (Object) This is frequently used when you have multiple types of generated content, such as blog posts and product reviews. (And as seen above, you can also set a default key to auto-wrap all MDX files.)
  • gatsbyRemarkPlugins: (Array of plugin objects) This allows us to use various Gatsby-specific remark plugins along with the MDX processing. The gatsby-remark-images plugin is often used here.
  • remarkPlugins: (Array of plugin objects) Similar to the above option, but for non-Gatsby dependent remark plugins.
  • rehypePlugins: (Array of plugin objects) Similar to above, but for rehype plugins.
  • mediaTypes: (Array of strings) Sets which media types are processed. (You probably won’t need to use this very often.)

Full details on the usage of these options can be found in the plugin’s documentation. These docs are excellent, and I highly recommend going over them after reading this article! 🔍

Basic Usage

The configuration we have so far can already process all .mdx files in our site. And thanks to Gatsby’s built-in behavior, if we add them to the src/pages/ directory they will also become pages automatically!

Let’s do that now by creating a simple MDX file at src/pages/mdx-intro/index.mdx. We’ll start off with some frontmatter and basic Markdown text, like a typical Markdown blog page would have:

/src/pages/mdx-intro/index.mdx

---
title: MDX is Magical!
path: /mdx-intro
date: 2019-08-25
---

# Hooray For MDX!

This will be like turbo-charged Markdown!

You can view this new page by visiting http://localhost:8000/mdx-intro in your browser.

You’ll probably recognize this page creation pattern if you went through the Your First Steps with Gatsby v2 article, the only difference being this is an MDX file instead of Markdown. This is nothing special or new so far. Let’s change that!

Using Components in MDX

One of the primary features of MDX is that we can import and use JSX components right inside of Markdown content.

To demonstrate this, let’s create a simple component at /src/components/TitleBar.js that will let us display a customized title bar.

/src/components/TitleBar.js

import React from 'react';

const TitleBar = ({ text, size, bkgdColor }) => (
  <div
    style={{
      margin: '2rem 0',
      padding: '2rem',
      backgroundColor: bkgdColor || '#fff',
    }}
  >
    <h2
      style={{
        fontSize: size || '18px',
        margin: 0,
      }}
    >
      {text}
    </h2>
  </div>
);

export default TitleBar;

Next, let’s update our MDX file to look like this:

/src/pages/mdx-intro/index.mdx

---
title: MDX is Magical!
path: /mdx-intro
date: 2019-08-25
---
import TitleBar from "../../components/TitleBar.js";

<TitleBar 
  size={"32px"} 
  bkgdColor={"#4aae9b"} 
  text={props.pageContext.frontmatter.title} 
/>

This will be like turbo-charged Markdown!

There are two things to note here:

  • First, we just imported and used a React component directly inside Markdown! Let that sink in for a moment, because this is an incredibly powerful concept. (Imagine blog posts with animated charts and/or dynamically loaded data, complex interactivity, and more.)
  • Second, you may have noticed that we are able to access the frontmatter values from props.pageContext.frontmatter. This can be quite useful, too!

Important: If your MDX files contain frontmatter, always place any import statements after the frontmatter block!

Go ahead and view the updated page in your browser, and try editing the size and bkgdColor props to watch it update. It’s a really simple example, but again: we are using a React component inside Markdown! Pretty sweet, right?!

Assigning Layouts

As mentioned in the configuration section, MDX provides us with an easy way to set up custom layouts. These layouts are convenient for wrapping additional styling and/or content around our MDX files.

Configuring default layouts

We can set up default layouts for our MDX files in gatsby-config.js, even for specific locations. Take a look at this example:

gatsby-config.js

module.exports = {
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `pages`,
        path: `${__dirname}/src/pages/`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `posts`,
        path: `${__dirname}/src/blog/`,
      },
    },
    {
      resolve: `gatsby-plugin-mdx`,
      options: {
        defaultLayouts: {
          posts: require.resolve("./src/components/blog-layout.js"),
          default: require.resolve("./src/components/layout.js"),
        },
      },
    },
  ],
}

In this example, we have configured our site so that all MDX files sourced from the /src/blog directory would use blog-layout.js as a layout/wrapper. We also set up a default config here, too.

Note: This behavior doesn't currently seem to work as expected with MDX files sourced from the pages directory. (But you can still wrap them with a default layout setting, like we have currently done.)

Manually assigning or removing layouts

Sometimes you will need to wrap a specific MDX file with a unique layout, or with no layout at all. This can be easily done by using JavaScript’s export default syntax inside our MDX files, which overrides any defaultLayout settings. We’ll cover that in the next section!

Importing Other MDX Files

In addition to importing/using JSX components, we can also import and use other MDX files as if they were components. (Hint: they actually are!)

Let’s create a new MDX file in our components directory, at /src/components/postSignature.mdx. We will use this at the bottom of our MDX page as an author’s signature.

/src/components/postSignature.mdx

##### Thanks for Reading!

*🐊 Al E. Gator | alligator.io | al@example.com*

export default ({ children }) => (
  <>
    {children}
  </>
)

Notice the export default statement at the bottom of the file. As mentioned in the previous section, this is how we can override our defaultLayout configuration settings. In this case, we’re exporting an empty <> wrapper around our signature instead.

Moving along, let’s import this MDX signature into our main MDX file, over at /src/pages/mdx-intro/index.mdx:

/src/pages/mdx-intro/index.mdx

---
title: MDX is Magical!
path: /mdx-intro
date: 2019-08-25
---
import TitleBar from "../../components/TitleBar.js";
import PostSignature from "../../components/postSignature.mdx";

<TitleBar 
  size={"32px"} 
  bkgdColor={"#4aae9b"} 
  text={props.pageContext.frontmatter.title} 
/>

This is like turbo-charged Markdown!

<PostSignature />

You should now see this signature at the bottom of the mdx-intro page. Awesome!! 😎

GraphQL Queries

Thanks to the plugin combo of gatsby-plugin-mdx and gatsby-source-filesystem, our MDX pages are also readily available to us via GraphQL queries.

We won’t spend much time on this, as this functionality is nearly identical to querying plain Markdown files in the same manner. (The only difference is that the MDX nodes are in allMdx and mdx instead of allMarkdownRemark and markdownRemark.)

Here’s an example query that would fetch the frontmatter of all available MDX files:

query {
  allMdx {
    edges {
      node {
        frontmatter {
          title
          path
          date(formatString: "MMMM DD, YYYY")
        }
      }
    }
  }
}

Providing Other Data

We can also provide additional data through our MDX files by using JavaScript’s export syntax, (not to be confused with export default as used above!) Any exported variables are added to the GraphQL schema automatically, so that we can use it when needed in GraphQL queries and/or during rendering.

Here’s some example “Food Truck Review” data that we could add to our MDX page:

export const myReviews = [
  {
    name: "Tim's Tacos",
    overall: 9,
    variety: 7,
    price: 8,
    taste: 9
  },
  {
    name: "Noodleville",
    overall: 7,
    variety: 5,
    price: 6,
    taste: 8
  },
  {
    name: "Waffle Shack",
    overall: 6,
    variety: 5,
    price: 4,
    taste: 6
  },
];

After adding that anywhere in the file, we could query the data in GraphQL by accessing allMdx.nodes.exports, like this:

query MdxExports {
  allMdx {
    nodes {
      exports {
        myReviews {
          name
          overall
          variety
          price
          taste
        }
      }
    }
  }
}

This is just a really basic demo, but this functionality can be used in incredibly creative and dynamic ways.

A Practical Example

Let’s finish up by adding a fun & practical example to our page. We’re going to use the myReviews data that we set up above to display an animated bar chart!

First, let’s add the Recharts library to our site. This is a powerful but lightweight charting library that I use frequently in my client projects.

$ yarn add recharts

Next, we will use Recharts to create a reusable bar chart component. Since this isn’t an article about Recharts, just go ahead and create a new file at /src/components/BarChart.js and paste in the following code:

/src/components/BarChart.js

import React, { PureComponent } from 'react';
import {
  BarChart,
  Bar,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
  ResponsiveContainer,
} from 'recharts';

const colorsList = ['#008f68', '#6db65b', '#4aae9b', '#dfa612'];

class ExampleChart extends PureComponent {
  render() {
    return (
      <div style={{ width: '100%', height: 350 }}>
        <ResponsiveContainer>
          <BarChart data={this.props.data}>
            <CartesianGrid strokeDasharray="2 2" />
            <XAxis dataKey="name" />
            <YAxis type="number" domain={[0, 10]} />
            <Tooltip />
            <Legend />

            {this.props.bars.map((bar, i) => (
              <Bar 
                dataKey={bar} 
                fill={colorsList[i]} 
                key={`bar_${i}`} 
              />
            ))}
          </BarChart>
        </ResponsiveContainer>
      </div>
    );
  }
}

export default ExampleChart;

Now we have a nice bar chart component set up, so we just need to import and use it in the MDX page. Here’s our final version:

/src/pages/mdx-intro/index.mdx

---
title: MDX is Magical!
path: /mdx-intro
date: 2019-08-25
---

import TitleBar from '../../components/TitleBar';
import PostSignature from '../../components/postSignature.mdx';
import BarChart from "../../components/BarChart";

export const myReviews = [
  {
    name: "Tim's Tacos",
    overall: 9,
    variety: 7,
    price: 8,
    taste: 9
  },
  {
    name: "Noodleville",
    overall: 7,
    variety: 5,
    price: 6,
    taste: 8
  },
  {
    name: "Waffle Shack",
    overall: 6,
    variety: 5,
    price: 4,
    taste: 6
  },
];

<TitleBar
  text={props.pageContext.frontmatter.title}
  size={'32px'}
  bkgdColor={'#4aae9b'}
/>


This page is built with turbo-charged Markdown!

#### My Food Reviews:

<BarChart 
  data={myReviews} 
  bars={["overall", "variety", "price", "taste"]} 
/>

<PostSignature />

You should now see a sweet-looking multi-colored bar chart that animates into view, and even has animated tooltips on rollover. 📊👈

And I’ll say it again:This is all inside a Markdown (MDX) page! Just think of all the interesting blog posts and pages you can create in no time flat…

Conclusion

We have really covered a lot in this intro to MDX with Gatsby! Hopefully it wasn’t too overwhelming, and you can see that this combo is a total game-changer for rapid website development.

However, we only scratched the surface of what is possible. From here, I recommend digging into the Gatsby docs section on MDX. It’s a rabbit-hole worth venturing down, I promise! 🕳🐇

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#