Using MDX in your Gatsby site

Published: April 15, 2020 | 10 min read

Phew! I'm glad to be done with the 100 Days of Gatsby forms challenge. Mostly because... hooray! I'm excited to work with MDX in Gatsby again, one of my favorite parts of the JAMstack. We used MDX heavily in O'Reilly Media's Design System documentation site, so I'm familiar with the syntax. It's been a year since I started using MDX with GatsbyJS, and I've been curious to learn what's changed by following the prompts of this latest challenge.


Blending Markdown and React using MDX

Adding gatsby-plugin-mdx` and all the companion dependencies allowed me to render any MDX files in my site with no issue. This section on writing pages in MDX in Gatsby was clear:

Pages are rendered at a URL that is constructed from the filesystem path inside src/pages. An MDX file at src/pages/awesome.mdx will result in a page being rendered at mysite.com/awesome. gatsby-plugin-mdx looks for MDX files and automatically transpiles them so that Gatsby internals can render them.

The documentation, however, was a bit unclear

Note: To query MDX content, it must be included in the node system using a source like the gatsby-source-filesystem plugin first. Instructions for sourcing content from somewhere like your /src/pagesdirectory can be found on the plugin’s README. Frontmatter is also available in props.pageContext.frontmatter and can be accessed in blocks of JSX in your MDX document:

What does node system mean in this context? Does this mean doing something in the gatsby-config file or the gatsby-node file?

The props.pageContext.frontmatter part was unclear… did I have to do a graphql query to find that data first, or is it done automatically under the hood? Is there an example of how you do this in an .mdx file?

Ahhh. I then read this in the docs:

When using frontmatter in an MDX file, all the imports need to occur after the frontmatter block.

I learned the hard way that if you do any file imports in your MDX file before writing your frontmatter information, the MDX thinks treats the three dashes closing the frontmatter block as a horizontal rule element.

Whoops! I wish I had seen that earlier in the order of this page. Fodder for a docs PR.

Also, I learned the importance of naming your graphql queries differently in the file. I'd receive this interesting CLI error when trying to import graphql into an MDX file and doing a page query in the file:

Multiple "root" queries found: "MyQuery" and "MyQuery".
Only the first ("MyQuery") will be registered.

Instead of:
   1 | query MyQuery {
   2 |   allMdx {
   3 |     #...
   4 |   }
   5 | }
   6 |
   7 | query MyQuery {
   8 |   allMdx {
   9 |     #...
  10 |   }
  11 | }

Do:
  1 | query myQueryAndMyQuery {
  2 |   allMdx {
  3 |     #...
  4 |   }
  5 |   allMdx {
  6 |     #...
  7 |   }
  8 | }

This can happen when you use two page/static queries in one file. Please combine those into one query.
If you're defining multiple components (each with a static query) in one file, you'll need to move each component to its own file.

File: src/pages/this-is-mdx.mdx

Docs reading order

In terms of docs order, I wanted the Using Frontmatter in MDX section to appear last in the page order, since I was unfamiliar with how frontmatter works and how it interacts with file/depencency imports.

Ideally, I would have liked to read the page in this order:

  • Importing JSX components and MDX documents
  • Using JavaScript exports
  • GraphQL queries
  • Using frontmatter in MDX, with a link to a description of frontmatter what it does, and the graphQL query part later?
  • Combining frontmatter and imports

Nesting MDX components!

One of the fun facts I discovered in this challenge: you can make a component using an MDX file and import it into another MDX file! There are some nuances to nesting MDX files to keep in mind, though. You should make sure you use a default export in your MDX-based component, to make sure it isn’t wrapped by the defaultLayout template set in your gatsby-plugin-mdx options in your gatsby-config.js file

/*
This default export overrides the default layout ensuring
that the FAQ component isn't wrapped by other elements
*/

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

In other words, here's a good explanation from the defining an (MDX) layout section:

If you have provided a default layout in your gatsby-config.js through the gatsby-plugin-mdx plugin options, the exported component you define from this file will replace the default.

So… if you don’t want your MDX file wrapped by another file, make sure you do an export in the MDX file, either by doing the an anonymous default export like above or by importing a component into your MDX file and then exporting that component at the end of your file.

The gatsby-plugin-mdx docs in general seemed straightforward to understand.

Another fun fact about nesting MDX files inside of MDX files. If you nest one MDX file into another, you can’t pass children into that imported MDX file like you can do with a standard React component.

There's a bit more information in the Using JavaScript exports section:

You don’t technically need to export MDX documents to import them in other files, but the unsaid thing is if you want that doc to use a layout other than the default or other layouts specified in the gatsby-config files, yes you need to export the MDX doc.


GraphQL queries in MDX files

The GraphQL queries section has some more infomation on exporting graphql queries:

Note: For now, this [exporting GraphQL queries] only works if the .mdx file exporting the query is placed in src/pages. Exporting GraphQL queries from .mdx files that are used for programmatic page creation in gatsby-node.js via actions.createPage() is not currently supported.

This means doing a graphql pageQuery in an MDX file will only work for MDX files in the src/pages folder

If you want to do a GraphQL pageQuery in an MDX file located out of the src/pages folder, you need to to it in the gatsby-node.js file and you also need to point to the correct folder you want to target using the gatsby-filesystem plugin in your gatsby-config.js file... (right?)


Gatsby MDX magic

There seemed to be a lot of features under the hood for rendering MDX files when those files are hosted in the src/pages folder. For example, one year ago, I had to wrap my layout components with the <MDXProvider> a year ago. Now it appears that you don’t need to do that anymore, it may be handled by default. Very nice.

I moved the MDX test pages I created into a posts/ folder inside the src/ directory, and added gatsby-plugin-page-creator in my gastby-config. This way, I could separate out .md and .mdx concerns while testing things.

After refactoring to have the index.js page point to the collection of posts pages and the about page…

Somehow, I could still generate pages from the posts/ directory using gatsby-plugin-page-creator without also pointing to the posts/ folder using gatsby-source-filesystem in my gatsby-config. Why? Is it because I did a graphql page query using a page in the src/pages folder?

Nope!

I discovered it’s because that posts/ folder was nested in the src/ folder. It looked like any files in the src/pages folder would be grabbed and rendered by Gatsby, as long as I used gatsby-plugin-page-creator to point to a subfolder. This made me curious, so I tried something else.

I commented out both gatsby-source-filesystem and gatsby-plugin-page-creator, and none of the blog posts in the posts/ folder rendered except for the one MDX file in the src/pages folder.

Something odd happened with Gatsby not recognizing templated layouts. It seemed like the cache would stick around and wouldn’t use the correct MDX template, no matter how many times I’d run npm run clean and then npm run start

The only way the new template would show up is if I made a change to the content in the actual MDX file first, and then the template around the MDX file would show the correct stuff. But... I couldn't reproduce this consistently and I couldn't figure out why the post template wasn't sticking. I wondered if I would have to add and remove that iframe element in that MDX file for things to stick? I also wondered if it had anything to do with using subfolders of src/pages directory in this case?

Regarding the documentation on programmatically generating pages, this information on creating pages from sourced MDX was very straightforward for me to understand.

Where I got tripped up, though, was in the instructions for using the <MDXRenderer> component and in the making a page template for your posts section.

Partially because I thought I had to do a GraphQL page (or static query) in the layout template to get the data (nope). I kept on getting an error about my data being an object and the layout not being exported correctly.

I was wondering if that a separate way to generate pages? Or a separate way to template pages? How does a page query affect a query coming in from gatsby-node?

After some serious tinkering, I discovered the real issue for me was: I was importing <MDXRenderer> from the wrong package dependency. I should have been importing it from gatsby-plugin-mdx rather than from "@mdx-js/react” . Whoops! My error stemmed from not staring hard enough at the code. Or staring too hard at it!

Once I pointed to the correct library, I could use <MDXRenderer> and didn’t need to use <MDXProvider> in the layout at all —even though the example guided you to do that — because you already made the query in the gatsby-node file.

In fact, I don’t think I’m using <MDXProvider> at all in these examples, yet. I will if I want to create shortcodes, though, and use the WrapRootElement component.

Later on, I may work on making a blog index following the docs instructions. I already have a bit of an index going, so this will be a nice way to try it differently.

Again, though, since the data is already queried in the gatsby-node file, do I need to do that page query in that particular page?

I still have the odd side effect of the posts page template not showing up consistently in my site. I’m wondering about the config settings there, and if it’s because the posts/ directory is a subdirectory of src/.

The Bonus Challenge

I tried out using the gatsby-plugin-embed package in my project. That plugin is sweet. I decided to keep in mind that I may need to use the <MDXEmbedProvider> in the future, in case I use more shortcodes and run into <MDXProvider> conflicts. I went and added that provider right into my <Layout> component instead to get things going.

Items to do later:

I really liked reading about the Lazy loading components example in the docs. That'd be something I'd love to add in the future.

That wraps up this challenge! Next for me to do... 100 Days of Gatsby — Challenge 6.

Back to top