Using MDX in your Gatsby site
Published: April 15, 2020 | 10 min readPhew! 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 atsrc/pages/awesome.mdx
will result in a page being rendered atmysite.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/pages
directory can be found on the plugin’sREADME
. Frontmatter is also available inprops.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 thefrontmatter
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 thegatsby-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 insrc/pages
. Exporting GraphQL queries from.mdx
files that are used for programmatic page creation ingatsby-node.js
viaactions.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.