Devdelly

Add categories and dynamically create category pages in a gatsby blog

October 19, 2019

Recently I wanted to add tags or categories to the posts in my gatsby blog. In this post we’re looking into how we can add tags to posts and generate separate pages for the posts of each category.

Initial Situation

The starting point for this was quite simple. The blog was based on the gatsby-starter-blog and contained some posts written in markdown with very little data in the frontmatter.

Example:

---
title: "Adding Google OAuth to Django"
description: How add a Google login button to your django login.
date: "2019-09-21"
featuredImage: ./django-google-oauth/login.jpg
---

We all love how simple it is to set an application up with django. ...

Now this is all great, but woudln’t it be cool to have also tags on each post and make it possible to easily view all posts of a category? Let’s see how we could achieve this

Adding Categories to the Posts

The first and simple step is to add categories to the posts. To do so we can add the categories of a post as an additional field in the frontmatter of each .md file:

---
title: "Adding Google OAuth to Django"
description: How add a Google login button to your django login.
date: "2019-09-21"
featuredImage: ./django-google-oauth/login.jpg
categories: ["development", "python"]
---

We all love how simple it is to set an application up with django. ...

We can now verify that this field from the frontmatter is accessible via graphql by running gatsby in development mode and heading over to GraphiQL running on http://localhost:8000/___graphql

If we run the query

query MyQuery {
  allMarkdownRemark {
    edges {
      node {
        frontmatter {
          title
          categories
        }
      }
    }
  }
}

the answer should now contain an entry with the categories:

{
  {
    "node": {
    "frontmatter": {
        "title": "Adding Google OAuth to Django",
        "categories": [
          "development",
          "python"
        ]
    }
  }
}

This means that we can now also use this part of the GraphQL query, when we query for the data to display.

Let’s say we have this super simple blog post template under src/templates/blog-post.js

import React from 'react'
import { graphql } from "gatsby"

class BlogPostTemplate extends React.Component {
  render() {
      const post = this.props.data.markdownRemark
      return (
        <Layout location={this.props.location} title={siteTitle}>
            <h1>{post.frontmatter.title}</h1>
            <div dangerouslySetInnerHTML={{ __html: post.html }} />
        </Layout>
  }
}

export const pageQuery = graphql`
  query BlogPostBySlug($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter {
          title
      }
    }
  }
`

To display the categories in this template we need to add them to our graphql query so that they’re available in the props:

export const pageQuery = graphql`
  query BlogPostBySlug($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter {
        title
        categories
      }
    }
  }
`

Now we can use the property in the React component:

class BlogPostTemplate extends React.Component {
  render() {
      const post = this.props.data.markdownRemark
      return (
        <Layout location={this.props.location} title={siteTitle}>
            <h1>{post.frontmatter.title}</h1>
            <h3>Categories: {post.frontmatter.categories.map(category => <div>{category}</div>})} </h3>
            <div dangerouslySetInnerHTML={{ __html: post.html }} />
        </Layout>
  }
}

Dynamically Creating Pages for each Category

This is the more interesting part which relies on the first part. Here we want to create a page for each category that we use in our posts. On the page of one category we then want to show all posts that belong to this category.

To achieve this we need to modify gatsby-node.js. This file already creates a page for each post via loading the post data from graphql and then invoking gatsby’s createPage method. To create our category pages we want to do the same thing, but instead of creating a page for each post, we want a page for each category. The simplest way we can achieve this is to also query the category on each post, collect all encountered categories and then create a page for each category:

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

  const blogPost = path.resolve(`./src/templates/blog-post.js`)
  return graphql(
    `
      {
        allMarkdownRemark(
          sort: { fields: [frontmatter___date], order: DESC }
          limit: 1000
        ) {
          edges {
            node {
              fields {
                slug
              }
              frontmatter {
                title
                categories
              }
            }
          }
        }
      }
    `
  ).then(result => {
    if (result.errors) {
      throw result.errors
    }

    // Create blog posts pages.
    const posts = result.data.allMarkdownRemark.edges

    posts.forEach((post, index) => {
      const previous = index === posts.length - 1 ? null : posts[index + 1].node
      const next = index === 0 ? null : posts[index - 1].node

      createPage({
        path: post.node.fields.slug,
        component: blogPost,
        context: {
          slug: post.node.fields.slug,
          previous,
          next,
        },
      })
    })

    // collect the encountered categories

    const categoriesFound = []
    posts.forEach(post => {
      post.node.frontmatter.categories.forEach(cat => {
        if (categoriesFound.indexOf(cat) === -1) {
          categoriesFound.push(cat)
        }
      })
    })

    // create a page for each category

    categoriesFound.forEach(cat => {
      createPage({
        path: `category/${cat}`,
        component: path.resolve(`./src/templates/category-page.js`),
        context: {
          category: cat,
        },
      })
    })

    return null
  })
}

Here category-page.js is a template that works analogous to blog-post.js.


A blog by Manuel about everything related to software development