How to make URL path prefix for section of website in Gatsby
alex
Posted on January 10, 2020
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
: destructurecreatePage
fromactions
- return an allMarkdownRemark graphql query named
blogposts
- use
createPage
and the results inres.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. 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.
Posted on January 10, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.