I got the opportunity to work with Gatsby and I have to say: it is a powerful tool and I am delighted with it.
I want to show you how you can generate navigation out of your files. We will not be using json or yaml files to store our navigation tree, but instead we will use what Gatsby and GraphQL offer us.
Setup
Let's create a new Gatsby project real quick. First, make sure you have the gatsby-cli
installed and if not, go ahead and install it:
npm install --global gatsby-cli
Next, we can use it with a starter to scaffold a new project. I used the Gatsby default starter:
gatsby new dynamic-navigation-demo https://github.com/gatsbyjs/gatsby-starter-default
After starting the local server we can visit our project on http://localhost:8000/
:
gatsby develop
Query pages
Gatsby generates static html files out of the react components in the pages
folder.
In the freshly created project we already have few: 404
, index
and page-2
.
Let's explore how we can generate a menu out of these pages.
1. Using gatsby-source-filesystem plugin
gatsby-source-filesystem
is a sourcing plugin that transforms files from your system into file nodes that can be used by Gatsby and GraphQL.
This plugin is already installed in the default starter and we can go ahead and start using it to source our pages.
In gatsby.config
we can create a new group sourcing the files in the pages
folder.
We can specify where to look for files with the path
option and name the group of sourced files with the name
option:
{
resolve: `gatsby-source-filesystem`,
options: {
name: `pages`,
path: `${__dirname}/src/pages/`,
},
},
Awesome, now we should be able to query the file nodes with GraphQL.
Gatsby offers out of the box a graphical interface powered by GraphiQL to explore your data with GraphQL queries.
Let's make use of this and go to http://localhost:8000/___graphql
.
We will query the allFile
root node as it will contain the file nodes sourced by the gatsby-source-filesystem
plugin.
Let's add the following query in the editor and run it:
{
allFile {
edges {
node {
id
name
}
}
}
}
Besides the pages files we are also getting the images, so let's filter them out by filtering down to file nodes within the pages
group we created earlier via the name option in the source plugin:
{
allFile(filter: { sourceInstanceName: { eq: "pages" } }) {
edges {
node {
id
name
}
}
}
}
Looking good, but we probably do not want to display the index or the 404 page within our menu. We can remove them by using a regex:
{
allFile(
filter: {
sourceInstanceName: { eq: "pages" }
name: { regex: "/^(?!index|404).*$/" }
}
) {
edges {
node {
id
name
}
}
}
}
Now we have only one page left: page-2
for us to display in the menu. Feel free to add some more pages!
We can wrap the header component in the header.js
file in the components
folder into a StaticQuery
component with the above query and iterate over the data to display the menu.
Now, the page name in the menu remains displayed with the file name: page-2
.
We can easily normalize it by removing the dashes and capitalizing the first letter, so we have Page 2
instead.
We can use the page name as is for the link urls. If a page is nested within a folder, this folder can be found in the relativePath
and you can use it to construct the link to the page.
Downsides:
- We need to exclude certain pages, for example
index
and404
and the regex might get longer and harder to comprehend if we need to exclude more. - We would also need to normalize page names by changing the dashes in the file name to spaces.
- You must name pages as you would want them to appear in the menu. It might happen that a file name serves well as a part of a url but not as menu item display name.
Upsides:
- Menu will remain up to date even after renaming files.
2. Using frontmatter
The gatsby-transformer-javascript-frontmatter
plugin allows you to add frontmatter to your pages which we could use to generate the navigation.
Let's install the plugin:
npm install --save gatsby-transformer-javascript-frontmatter
Add to gatsby config:
{
resolve: `gatsby-source-filesystem`,
options: {
name: `pages`,
path: `${__dirname}/src/pages/`,
},
},
`gatsby-transformer-javascript-frontmatter`,
Next, add frontmatter to your pages by exporting a constant named frontmatter:
export const frontmatter = {
title: "Home Page",
path: "/",
};
Now we can query the pages and the frontmatter and use it to display the menu:
{
allJavascriptFrontmatter {
edges {
node {
frontmatter {
title
path
}
}
}
}
}
Downsides:
- Error prone when renaming files as you will have to manually update the path name.
Upsides:
- Allows adding addtional data like menu display name and categories for example.
3. Using gatsby-source-filesystem plugin and frontmatter
We cam combine these two plugins when quering for data:
menuItems: allFile(filter: {
sourceInstanceName: {eq: "pages"},
name: {regex: "/^(?!index|404).*$/"}
}) {
edges {
node {
id
name
relativeDirectory
childJavascriptFrontmatter {
frontmatter {
title
}
}
}
}
}
Now we can use the frontmatter title in the menu and the name for the page url.
Downsides:
- Getting properties can get a little long-winded:
childJavascriptFrontmatter.frontmatter
.
Upsides:
- Pages remain up to date after renaming a file.
- It is possible to add additional information like display name, sorting order or category.
Conclusion
We saw three ways to query pages and use the data to generate a menu. There are still issues to be solved like menu items order or nested menu items for example, but this is a good starting point.