By Paul Scanlon

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.

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:

  1. You want to source from outside of src/pages
  2. 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 👇

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!

Hey!

Leave a reaction and let me know how I'm doing.

  • 0
  • 0
  • 0
  • 0
  • 0
Powered byNeon