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 Kruisz a Freelance Software Developer based in Vienna