Jan 2019

Query pages in a Gatsby project

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 and 404 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.