Change the URLs of posts in Gatsby

Last update: August 14, 2019

Table of contents:

If you want to create an MDX blog in Gatsby, you have to do 3 things:

  • Install 3 packages: gatsby-plugin-mdx, @mdx-js/mdx, and @mdx-js/react.
  • Add the gatsby-plugin-mdx to your plugins array inside gatsby-config.js.
  • Create some MDX files inside src/pages.

And now you have an MDX blog. If you create a src/pages/post-1.mdx file, Gatsby will create a page for that file and will be available at:

http://example.com/post-1/

But sometimes, you want your posts to be under a /posts/ URL. For example:

http://example.com/posts/post-1/

The standard way to do this is to create the MDX pages yourself with the createPages API. But creating the pages this way requires 3 extra steps:

  • You have to implement the createPages API in your gatsby-node.js to create the pages and maybe the onCreateNode to pass some context to your pages.
  • You have to install and configure the gatsby-source-filesystem plugin to look for MDX files. This is important because otherwise, your MDX files won’t be available to GraphQL queries; and you want that for the first step.
  • Finally, you have to create a post template for your posts, and use it when you create the pages (in the first step).

But that’s only one way to create custom URLs for your posts, and, let’s be honest, it’s kind of an overkill. Let’s see now some alternatives you have.

Folder solution

If you want to change only the URL of your posts, you can just place your MDX files inside folders. For example, if you want a bunch of notes under the /notes/ URL, create a notes folder, and place your notes inside that folder. Consider the following structure:

src
|__ pages
|____ index.js
|____ page-2.js
|____ notes
|______ note-1.mdx
|______ note-2.mdx

Simple, right? Although it’s simple, this solution has some limitations. For example, you can’t pass context data to your page or use a custom template for the posts. If you want to have those options, consider the second solution.

onCreatePage API

I said earlier that you can use the createPages API to programmatically create pages. But in addition to the createPages, there is another Node API: the onCreatePage. This API is called, as you may have guessed, when a page is created. But an important detail here is that is not called for those created by the createPages.

So the plan is to use the onCreatePage to delete the created pages from src/pages and then create new pages with the updated path. We don’t want to delete all the pages of course. Only the MDX pages that will be used as posts. To identify them, you can add a frontmatter field called type with a value of post at the top of the file. For example:

---
type: post---

# Post 1

Content

See it in action

To see this in action create a new project with the hello-world starter:

mkdir change-post-url
cd change-post-url
gatsby new . https://github.com/gatsbyjs/gatsby-starter-hello-world.git

Install gatsby-plugin-mdx and the other dependencies:

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

Configure the plugin in your gatsby-config.js:

// gatsby-config.js

module.exports = {
  plugins: ["gatsby-plugin-mdx"]
};

Then, create an MDX file inside src/pages that will be used as a regular page:

<!-- src/pages/page-2.mdx -->

# Page 2

This is the second page in MDX.

import { Link } from "gatsby"

<Link to="/">Go back to homepage</Link>

And another MDX file that you’ll use as a post:

<!-- src/pages/post-1.mdx -->

---

type: post

---

# Post 1

This is my first post.

After you do all the above, you end up with 3 pages:

  • /
  • /page-2/
  • /post-1/

What’s left now is to change the URL of /post-1/ page to /posts/post-1/. So let’s implement the onCreatePage API in gatsby-node.js:

// gatsby-node.js

exports.onCreatePage = ({ page, actions: { createPage, deletePage } }) => {
  const frontmatter = page.context.frontmatter;
  if (frontmatter && frontmatter.type === "post") {
    deletePage(page);
    createPage({
      ...page,
      path: `/posts${page.path}`
    });
  }
};

If the page has a frontmatter object in the context, then we’re dealing with an MDX page. To check if it’s also a post, you look inside that frontmatter object for a type field with a value of “post”. If all the previous are true, you delete the page, and create a new one with the updated path:

* Notice that I change only the path in the createPage method. As I mentioned earlier, if you wish, you can also pass a context or a template.

After implementing the onCreatePage API, you end up with the following pages:

  • /
  • /page-2/
  • /posts/post-1/

You can do that for any entity; it doesn’t have to be a post. If you’re building a hotel website, that entity could be a room or an offer.

Make the MDX files available to GraphQL

You may want to create a page that lists all the posts. So you want your MDX pages to show up in GraphQL queries. To do that, install the gatsby-source-filesystem plugin:

yarn add gatsby-source-filesystem

And configure it in your gatsby-config.js to look for files in your src/pages:

// gatsby-config.js

module.exports = {
  plugins: [
    "gatsby-plugin-mdx",
    {      resolve: "gatsby-source-filesystem",      options: {        name: "mdx",        path: `${__dirname}/src/pages`      }    }  ]
};

If you want to show only the posts, you can write a GraphQL query like the following:

query AllPostsQuery {
  allMdx(filter: { frontmatter: { type: { eq: "post" } } }) {
    nodes {
      id
      timeToRead
      excerpt(pruneLength: 200)
    }
  }
}

Other things to read

Popular notes

Other posts