Adding Tags to Posts in Gatsby Site

Note: This is part 4 of five-parts adding functionality to Gatsby site series. This learning post is still in active development and updated regularly.

In the previous learning-note posts series, creating posts with markdown, how images & prismJS syntax highlighter, and typography are added to the posts were described. The next logical step to add functionality to the site is to add tags at the bottom of each posts to list or view all tags to allow users to browse related blog posts content.

Learning series
Part 1: Adding Syntax highlighter with Prismjs
Part 2: Styling Gatsby Site with Typography & Sass Plugins
Part 3: Learning to Work with Images in Gatsby Sites
Part 4: Adding Tags to Posts in Gatsby Site (this post)
Part 5: Adding Navigation to Posts in Gatsby Site

In this part 4 of five-parts learning-note post series, we will explore how tags are added to markdown posts in a Gatsby site. In this post, the following step-by-step process outlined in the Gatsby Document Creating Tags Pages for Blog Posts are followed.

  • Adding tags to markdown files
  • Writing query to get all the tags in the posts
  • Creating a tag page template to display tag post
  • Updating gatsby-node.js to display pages using tag template
  • Updating blog-post.js template to display tags inline
  • Creating tag.js index page to display all tags
Starting Point

As a starting point for this post, the working site used in Part 3 of this learning series is used. To start fresh, a new site named ‘hello-plugin‘ needs to be setup with the Gatsby default starter hello-world as described previously. How to add markdown posts to a fresh installed site is described in previously in this post.

Step 1: Add Tags to Markdown Frontmatter

Tags are defined and added in the frontmatter of the markdown file as array of fields as shown below (line 4).

---
title: Adding Tags to Markdown File
author: GatsbyJs
tags: ['images', 'gatsby', 'adding-tags']
---

In this post, tags fields are added in the post.

After restarting the developer server, the tags fields can be viewed in a server (localhost:8000) are available for GraphQL query.

Step 2: Get All Posts Tags with GraphQL Query

When viewed the site at localhost:8000/___graphql the above tags fields (other fields in frontmatter) are displayed as data layer.

// src/pages/tags.js
export const pageQuery = graphql`
query {
  allMarkdownRemark(
    limit: 2000) {
    group(field: frontmatter___tags) {
      fieldValue
      totalCount
    }
  }
}
`

The above GraphQL query, taken from the Gatsby Docs, groups posts in frontmatter tags field (line 6) and returns each tag with number of posts with total value (lines: 7-8). Now this query can be used to create tags.js index page to display tag with number of posts (see below Step 6).

Step 3: Create tag-templte.js Page to Display Post

Just like creating individual post pages with createPages API in gatsby-node.js (described in step 4, below) and page template blog-post.js (described in a previous post), a src/templates/tag-template.js page template is created to display list of posts with a selected tag as shown below:

// src/templates/tag-template.js
import React from 'react';
import { Link, graphql } from 'gatsby'
import Layout from '../components/layout';

function Tags(props) {
  const posts = props.data.allMarkdownRemark.edges;
  const { tag } = props.pageContext;
  return (
    <Layout>
      <div className="tag-header">
        <h1>{`Tag: ${tag}`}</h1>
        <p className="tag-des">Total {props.data.allMarkdownRemark
         .totalCount} posts | <Link to="tags"> All Tags</Link></p>
       </div>
       <div className="tags">
         { 
          posts.map(({ node }, i) => (
           <ul className="taglist">
             <li>
               <Link to={node.fields.slug} key={i} >
               <div className="tagtitle">
                 {node.frontmatter.title} 
               </div>
              </Link>
               <p> {node.excerpt} </p>
            </li>
          </ul>
           ))             
         }
      </div>
    </Layout>
  )
}

export default Tags;

export const query = graphql`
query TagsQuery($tag: String!) {
allMarkdownRemark(
    limit: 2000
    sort: { fields: [frontmatter___date], order: DESC }
    filter: { frontmatter: { tags: { eq: $tag } } }
  ) {
    totalCount
    edges {
      node {
        frontmatter {
          title
          tags
        }
        fields {
          slug
        }
        excerpt(pruneLength: 200)
      }
    }
  }
}
`

The GraphQL query in the post above (lines: 38-60) is designed to list of the posts with the selected tags value only (lines: 48-51, 55) displaying post title and excerpts (lines: 16-31).

Step 4: Update gatsby-node.js to Display Pages Using Tag Template

After creating tag-template.js template (step 3) to display tags pages, just like posts pages, the gatsby-node.js should be updated to (i) create array of unique tags from the blog posts, (ii) add tags field in the GraphQL query, and (iii) create individual tags using the tag template created in the previous section (step 3).

// gatsby-node.js
const path = require("path")
const { createFilePath } = require(`gatsby-source-filesystem`)
const _ = require('lodash');

exports.createPages = ({ actions, graphql }) => {
  const { createPage } = actions
    return new Promise((resolve, reject) => {
        resolve(graphql(`
     {
      allMarkdownRemark(
        sort: { order: DESC, fields: [frontmatter___date] }
        limit: 1000
      ) {
        edges {
          node {
              fields{
                  slug
              }
            frontmatter {
              title
              tags
            }
          }
        }
      }
    }
  `).then(result => {
      if (result.errors) {
        console.log(result.errors)
        return reject(result.errors)
      }

    const blogTemplate = path.resolve('./src/templates/blog-post.js')
    const tagsTemplate = path.resolve('./src/templates/tag-template.js');
    const posts = result.data.allMarkdownRemark.edges

    //All tags
    let allTags = []
    // Iterate through each post, putting all found tags into `allTags array`
      _.each(posts, edge => {
      if (_.get(edge, 'node.frontmatter.tags')) {
        allTags = allTags.concat(edge.node.frontmatter.tags)
       }
     })
     // Eliminate duplicate tags
       allTags = _.uniq(allTags)
        // make tag pages  
        allTags.forEach((tag, index) => {
           createPage({
           path: `/${_.kebabCase(tag)}/`,
           component: tagsTemplate,
           context: {
                tag,
           }
         })
      })

     //create pages
  // ...
  })
 }

// ...

In the code example above, the lodash (const _ = require('lodash') ) is included (line 4) to get the unique tags from all the posts. Then all tags are created by iterating through each post, putting all found tags into allTags (lines: 39-45). Any duplicate tags are eliminated using allTags = _.uniq(allTags) (line 47).

Next tag page is created by adding a tag.forEach to iterate each tag (line 49) and calling createPage API with the path, the component, and the context (lines: 50-56). The $tag variable is passed to the GraphQL in tag-template.js (line 12, shown in step 3) via the context field from createPage in gatsby-node.js file (line 54, above).

After restarting the development server and view at localhost:8000 in a browser, list of posts under a selected tag will be displayed.

Figure: Screenshot of list of tag posts displayed (left) and all tags with total counts in the site (right).

In the example above list of all 7 posts (shown partially) with tag: react with post title and excerpt are displayed (left, A).

Step 5: Update Blog-post.js to Display Tags Inline With Posts

Add the following code snippet to the blog-post template after the line dangerouslySetInnerHTML= ... to display the tags field inline.

//src/templates/blog-post.js
//...
// add after dangerouslySetInnerHTML ...
    <div className="footer-tag">
      <span>Tagged in:</span>
      {tags.map((tag, i) => (
      <a href={`/${tag}`} key={i} style={{ marginLeft:"10px" }} >{tag}</a>
       ))}
    </div>
// add next, prev links here
//...

Restart developer server and view the post at localhost:8000 in a browser, which should display the tags at the bottom of the post as shown in a screenshot below.

Figure: Screenshot showing tags field in frontmatter of markdown file (left) and tags field displayed at the bottom of a post (right).
Step 6: Create Tags.js Index Page to Display All Tags

In earlier section in Step 2, GraphQL query to display all post tags were created (lines: 26-36, shown below). Now, lets use the query to display all unique post tags along with number of posts with that tag by creating a src/pages/tags.js page component as shown below:

// src/pages/tags.js
import React from 'react';
import { Link, graphql } from "gatsby"
import Layout from '../components/layout';

function TagsPage(props) {
  const data = props.data.allMarkdownRemark.group
  return (
    <Layout>
       <div className="tags">
           <h1>All Tags</h1>
             {
              data.map(tag => (
                <Link to={`/${tag.fieldValue}`} >
                  {tag.fieldValue} {`(${tag.totalCount})`}
                 </Link>
                ))
             }
       </div>
   </Layout>
  )
}

export default TagsPage;

export const pageQuery = graphql`
query {
  allMarkdownRemark(
    limit: 2000) {
    group(field: frontmatter___tags) {
      fieldValue
      totalCount
    }
  }
}
`

The example code above (lines: 10-19) displays a list of all tags as an index page at /tags using fieldValue and totalCount from the query field (lines: 31-32).

When viewed after development server restart, you should see a list of all tags used and their totals at localhost:8000/tags as shown in screenshot (right, marked as B) in step 4.

Wrapping Up

In this learning-note posts, basic steps to display tags in a markdown post, (i) adding tags to frontmatter metadata, creating tags page to collect all the tags and display lists of posts filtered by a specific tag at /tags/:tag, and linkable tags in a blog posts were discussed. In the next post, adding next and previous posts navigation at the bottom of each post for easier navigation will be discussed.

Next Post: Adding Navigation to Posts in Gatsby Site

Useful resources

While preparing this post, I have referred the following references extensively. Please to refer original posts for more detailed information.