Gatsby File System Route API: Multiple Source MDX
In this post I’m going to provide an example of how to use Gatsby’s File System Route API to source MDX files from multiple locations anywhere in you projects directory structure.
Links
When I first read the docs for the
File System Route API I didn’t initially make
the connection between “file system” and “route”, but in short the idea behind this API is that Gatsby will create URLs
in accordance with how you’ve organized your pages
directory, E.g
|-- src
|-- pages
|-- about.js
Which would produce an about page on a route or URL like this, E.g http://localhost:8000/about or
www.example.com/about
Using the file system in this way it makes it quite easy to determine the eventual URL of any given page of your website, and this also works for nested directories, E.g
|-- src
|-- pages
|-- company
|-- about.js
Which would produce a route or URL like this http://localhost:8000/company/about or
www.example.com/company/about
The above is actually default Gatsby behavior and uses the Files System Route API under the hood. In this post I’ll be
taking it a step further and demonstrate the power of “programmatically” naming directories and files. For completeness
I did want to point out that Gatsby is able to create routes from any .js|tsx|md|mdx
file(s) found under src/pages
However things get a little more dicey if:
- You want to source from outside of
src/pages
- If the “page” is used to programmatically render content written in MDX
To address one issue at a time. First I’ll deal with sourcing from outside of the src/pages
directory
1. gatsby-source-filesystem
For example, If I wanted to source my MDX from a directory called things
which has x2 sub directories called posts
and projects
E.g
|-- src
|-- things
|-- posts
|-- post-1.mdx
|-- projects
|-- project-1.mdx
|-- pages
I’d install gatsby-source-filesystem
, add the plugin to gatsby-config.js
and use multiple resolvers to source from
multiple file system locations. E.g
// gatsby-config.js
module.exports = {
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/src/things/posts`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/src/things/projects`,
},
},
],
};
2. gatsby-plugin-mdx
Because my “source” is MDX I’d also install gatsby-plugin-mdx
and add the plugin to gatsby-config.js
// gatsby-config.js
module.exports = {
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/src/things/posts`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/src/things/projects`,
},
},
+ `gatsby-plugin-mdx`,
],
}
Whilst this will indeed “source” my MDX from the above locations they’re not actually pages yet!
To turn them into pages Gatsby needs to be told about them… and Gatsby is expecting to be told about them in
src/pages
src/pages
It’s here were I can leverage the magical curly braces that you might have seen mentioned in the docs
In the demo repo you’ll see I have something like this 👇
Just a heads up but, notice content
is a sub directory of pages
which is where the magic will happen
|-- src
|-- things
|-- posts
|-- post-1.mdx
|-- projects
|-- project-1.mdx
|-- pages
|-- content
|-- {mdx.frontmatter__variant}
|-- {mdx.slug}.js
frontmatter
If you’re familiar with MDX you’ll probably already know about frontmatter
but for those who don’t frontmatter
is a
special way to store and then later query and render additional information about an MDX file.
Here’s the frontmatter
used in
post-1.mdx
from the demo repo
---
title: A post called post 1
variant: posts
tags: [React, Gatsby, MDX]
---
...
and here’s the frontmatter
used in
project-1.mdx
from the demo repo
---
title: A project called project 1
variant: projects
client: Boogy Inc
---
...
Since posts
and projects
will likely be used differently there are some differences in the frontmatter
but, both
contain a field called variant
.
The variant
is what’s used by the File System Route API and can be seen in the above directory name
{mdx.frontmatter__variant}
The double underscore denotes that variant
is a nested property of the frontmatter
object ☝️
For clarity if I run the below query in GraphiQL
{
mdx {
frontmatter {
variant
}
}
}
I’ll see something similar to this 👇
{
"data": {
"mdx": {
"frontmatter": {
"variant": "posts"
}
}
}
}
By translating src/pages/content/{mdx.frontmatter__variant}
Gatsby will produce URLs that looks like this 👇
http://localhost:8000/content/posts orwww.example.com/content/postshttp://localhost:8000/content/projects orwww.example.com/content/projects
Now that I’m able to create a URL from a directory structure and a programmatically generated directory name I can move on to creating a page for each MDX file that’s been sourced.
The next bit is a bit vexing so strap in 😬
Programmatic page
If I run the below query in GraphiQL
{
mdx {
slug
}
}
I’ll get an output something like this 👇
{
"data": {
"mdx": {
"slug": "post-1"
}
},
}
The slug
is the actual file name on disk E.g post-1.mdx
, and I use this to create the next part of the URL by using
the curly braces as a “programmatic page” file name E.g {mdx.slug}.js
The reason I have a programmatic page is because I’ll want to process all the posts
and projects
MDX files in the
same way.
By process I mean for both posts
and projects
I’ll want to do the following:
- Turn them in to pages
- Query the
frontmatter
- Determine which page template to use
- Add a “Back” link
// src/pages/content/{mdx.frontmatter__variant}/{mdx.slug}.js
import React, { Fragment } from 'react';
import { graphql, Link } from 'gatsby';
import PostsTemplate from '../../../templates/posts-template';
import ProjectsTemplate from '../../../templates/projects-template';
const MdxPage = ({
data,
data: {
mdx: {
frontmatter: { variant },
},
},
}) => {
const templates = {
posts: <PostsTemplate data={data} />,
projects: <ProjectsTemplate data={data} />,
};
return (
<Fragment>
<Link to='/'>Back</Link>
{templates[variant] ? templates[variant] : null}
</Fragment>
);
};
export const query = graphql`
query ($id: String) {
mdx(id: { eq: $id }) {
frontmatter {
title
variant
tags
client
}
body
}
}
`;
export default MdxPage;
You can see the src
file for the above
here
The main point to notice is that i’m using the variant
to conditionally render a page template.
Page Template
A page template can be used to render the specific data found in the frontmatter
. As mentioned above posts
and
projects
have slightly different frontmatter
For example posts
have a field in the frontmatter
called tags
which is an array of strings. In
posts-template.js
I map over this array to create a list.
In
projects-template.js
there’s no need to create a list for the tags because there’s no tags
field present in the frontmatter
. Instead
there’s a client
field in the frontmatter
which I render as an <h3>
It’s an odd concept to get your head around at first. src/pages
is in many ways completely un-related to where you
chose to locate your source files and those curly braces are a bit odd when you first see them.
What I have found however is that by running the query in GraphiQL first I’m able to better visualize what the actual value inside the curly braces will be and in turn what the resulting URL will look like, you might find that helpful too 🤷♂️
To note, the File System Route API can also be used with data sourced remotely. In past projects i’ve used it in
combination with sourceNodes,
createNodeId
and gatsby-node.js
See you around!