DEV Community

Mihai Bojin
Mihai Bojin

Posted on • Originally published at mihaibojin.com on

Dynamic page generation in GatsbyJS

Photo: Water, splashing
"Photo by isaac sloman on Unsplash"

🔔 This article was originally posted on my site, MihaiBojin.com. 🔔


Lately, I've been working hard on generating content for my site. So naturally, it falls into multiple categories.

I found myself copy-pasting pages and changing titles, descriptions, and GraphQL queries, a bit too much for my liking.

I didn't feel inspired!

And decided to do something better...

Here's what I did to dynamically generate category pages using a common template.

Create category metadata in gatsby-config.js

First, I defined my categories in gatsby-config.js's siteMetadata array.

  "categories": [
    {
      "title": "Blog",
      "href": "/blog",
      "description": "My blog. I write about my journey as a software engineering lead and content creator.",
      "pageHeading": "From the blog",
      "pageSubtitle": "Explore some of my blog posts as I embark on a journey to build my site from scratch using GatsbyJS and TailwindUI."
    },
    ...
  ]
Enter fullscreen mode Exit fullscreen mode

Dynamically create pages in gatsby-node.js

Then, I extended Gatsby's page creation API:

exports.createPages = async ({ graphql, actions, reporter }) => {
  await createCategoryPages({ graphql, actions, reporter }); // a new async function I created
};
Enter fullscreen mode Exit fullscreen mode

I split various functionality by context. I defined one function responsible for creating categories.

async function createCategoryPages({ graphql, actions, reporter }) {
  const { createPage } = actions;

  // logic here, see below
}
Enter fullscreen mode Exit fullscreen mode

I loaded the site's metadata in the function's body; I needed information about my categories and the author.

  const result = await graphql(
    `
      {
        site {
          siteMetadata {
            categories {
              title
              href
              description
              pageHeading
              pageSubtitle
            }
            author {
              name
              href
            }
          }
        }
      }
    `,
  );
Enter fullscreen mode Exit fullscreen mode

Next, a validation step. I have skipped the validation code, but basically, it's a null check on these objects, followed by a call to reporter.warn or reporter.panicOnBuild.

  const siteMetadata = result.data.site?.siteMetadata;
  const categories = siteMetadata?.categories;
  const author = siteMetadata?.author;
Enter fullscreen mode Exit fullscreen mode

Since the component doesn't change, I cached it before iterating.

  const component = path.resolve(`./src/components/category.js`);
Enter fullscreen mode Exit fullscreen mode

Finally, I iterated through all my categories to create each page.

  categories.forEach((page) => {
    createPage({
      path: page.href,
      component,
      context: {
        ...page,
        author,
      },
    });
  });
Enter fullscreen mode Exit fullscreen mode

Note: the component will need to load a list of articles for each category. Since the site has articles in multiple categories, we need to pass something to construct the correct query. In Gatsby, this is achieved by passing data via the page's context. GraphQL page queries can make use of any variables passed by context.

Using a variable to filter articles by category

For my site, I chose to filter by each category's title. I could have easily filtered by the category's href, but I don't plan on changing either much. Time will tell if this was the right choice...

I updated the old query:

query {
    allMarkdownRemark(
        sort: { fields: [frontmatter___date], order: DESC }
        filter: {
          frontmatter: {
            category: {
              title: { eq: "A category title" }
            }
          }
        }
    ) {
        nodes {
        ...
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

by giving it a name and passing the $title variable.

This variable gets automatically populated from the page's context - 🪄 magic 🪄!

query CategoryQuery($title: String!) {
    allMarkdownRemark(
        sort: { fields: [frontmatter___date], order: DESC }
        filter: {
          frontmatter: {
            category: {
              title: { eq: $title }
            }
          }
        }
    ) {
        nodes {
        ...
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

All I had left to do was to load the page's data and populate my template:

export default function Page({ data, pageContext }) {
  const {
    title,
    description,
    pageHeading,
    pageSubtitle,
    author,
  } = pageContext;
  const posts = data.allMarkdownRemark.nodes;
  return (
    <>
        ...
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Et voila! Dynamically generated pages:


If you liked this article and want to read more like it, please subscribe to my newsletter; I send one out every few weeks!

Discussion (0)