How to make URL path prefix for section of website in Gatsby
Published: 2020-01-10 12:24:51

Let's say we want all our Gatsby blog posts to have a /blog/ path prefix. Like: example.com/blog/20202-01-10-i-went-to-mars.

It can be any other section but let's go with blog for this article.

Most tutorials say to put the blog posts in src/pages/blog. The post URLs will automatically come out the way we want. However, that assumes we write each blog post as a JSX component and with its own graphql query if there's markdown involved. What if we use a page template for the blog posts? It won't work anymore. Didn't for me. *(Update 24 Feb 2020: it does work, I was being stupid somehow back then, I think. But this article presents another valid way to do it anyway.)*

Instead of doing that, let's put blogpostTemplate.js in src/templates/blogpostTemplate and each blog post in src/blogposts/individual_post_dir/post.md. Like this:

```
src/templates/
└── blogpostTemplate.js

src/blogposts
└── 2020-01-01-individual-post-dir
    ├── 2020-01-01-first-post.md
    └── shoe-800x600.jpg
```

I won't discuss how to make a page template. It's easy to find resources on that. Anyway, presumably you're here because you've already made a template and found path prefixes broken. I'll focus only on creating the path prefix.

# Usual page template setup in gatsby-node.js

Now, open up gatsby-node.js in the project root directory. If you've already created a page template for the blog (or whatever other section of your site), the following code will be familiar to you.

There are two or three ways to do it. Some use async/await, some use `then`. I feel it's more a matter of aesthetic taste than anything else. I prefer async/await but for goodness only knows what reason I went with `then` for this project:

```
const path = require("path")
const { createFilePath } = require(`gatsby-source-filesystem`)

exports.createPages = ({ actions, graphql }) => {
  const { createPage } = actions

  return graphql(`

      blogposts: allMarkdownRemark(
        filter: { fileAbsolutePath: { glob: "**/src/blogposts/*/*.md" } }
      ) {
        edges {
          node {
            html
            frontmatter {
              path
            }
            fields {   // Take note of this part
              slug
            }
          }
        }
      }
    }
  `).then(res => {
    if (res.errors) {
      return Promise.reject(res.errors)
    }

    res.data.blogposts.edges.forEach(({ node }) => {
        createPage({
          path: node.fields.slug,  // Look, it's here again
          component: path.resolve("src/templates/blogpostTemplate.js"),
          context: {
            slug: node.fields.slug,  // We'll create it in a bit
          },
        })
      })

  })
}

```

The main portions in the code above are:

- `const { createPage } = actions`: destructure `createPage` from `actions`
- return an allMarkdownRemark graphql query named `blogposts`
- use `createPage` and the results in `res.data.blogposts.edges.forEach()` to create pages for each individual blog post using blogpostTemplate.

All pretty standard as a hundred and one tutorials out there will tell you. `fields { slug }` is important. It doesn't make sense now. But we'll make it in the next section. Gatsby will use this to create the path prefix.

# The path prefix

Next, the exciting part. We create the path prefix. Right below the above code in gatsby-node.js, we paste in this:

```
exports.onCreateNode = ({ node, getNode, actions }) => {
  const { createNodeField } = actions
  if (
    node.internal.type === `MarkdownRemark` &&
    node.fileAbsolutePath.includes("blogposts")
  ) {
    const slug = createFilePath({ node, getNode, basePath: `src/blogposts` })
    createNodeField({
      node,
      name: `slug`,
      value: `/blog${slug}`, // Here we are, the path prefix
    })
  }
}
```

Whenever we run `Gatsby develop` or `build`, Gatsby creates nodes for all our directories and markdown files. It then uses these nodes for routing and linking and whatever.

As the name of the function above suggests, `onCreateNode` does something upon the creation of a node. We can do whatever we want with it. For our purposes, we have an if statement that checks if the file is markdown, and if it's contained in the blogposts directory.

If it is, then we use `createFilePath` to create a slug. The slug is just the blog post directory concatenated with the blog post filename.

To add the /blog/ path prefix, add it to value in `createNodeField` as shown above: `/blog${slug}`.

Gatsby will now use this value to create the paths to all our blogposts.

Following the example in the directory tree above, our post will be at example.com/blog/2020-01-01-individual-post-dir/2020-01-01-first-post.

# Adapting the blog post template

Remember to edit blogpostTemplate.js. Tutorials usually tell us to have this query or very similar in it:

```
export const query = graphql`
  query($path: String!) {
    markdownRemark(frontmatter: { path: { eq: $path } }) {
      html
      frontmatter {
        title
        ...
      }
    }
  }
`
```

It looks for a `path` tag in the markdown frontmatter. However, we don't have `path` anymore. We have `slug`.

If you're already using `slug`, lucky you. If you aren't, change it:

```
export const query = graphql`
  query($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      ...
    }
  }
```

# The end

For some reason, it's been frightfully difficult to find resources on how to do this. The best resource so far is actually the official Gatsby tutorial [part 7](https://www.gatsbyjs.org/tutorial/part-seven/). I think it's worthwhile to at least skim it for a better sense of things. In fact, I'm sure if people went through it, they'd figure things out faster than I did. But, I think, because the tutorial doesn't explicitly talk about prefixes, most people skip it.

A whole lot of people are asking about this online and trying all kinds of funny code acrobatics so I thought to write this down.

I may have misconceptions about how things are done in Gatsby. Or I've got things wrong. Or maybe you have a quicker, or more efficient way of doing this. Feel free to tell me these things.