Flawed Engineer

How to add Tags to your Gatsby blog - Part 1

March 30, 2020 - 5 min read - JavascriptReactGraphql

One of the first things I wanted for my personal blogs was…Tags! I wanted a very common way to organize my posts in categories so that I would be able to explore specific topics and let my readers do the same. Following a quick guide on how to add tags to your blog posts with Gatsby!

First we will show tags on each article in the home page (part 1), then we will make sure that clicking on any tag will list all posts associated with it (part 2). Let’s get started!

1. Create a new blog with Gatsby (if you don’t already have one)

I’ll cut this part short, simply follow the link (gatsby-starter-blog) and the instructions listed in there. It will quickly show you how to start a basic blog.

2. Let’s create our first blog post

The starter should come with three sample posts. That’s plenty for us to demonstrate how to add tags. If you go into content/blog you will see the list of blog posts. Let’s start with the very first one (the blog will order them chronologically) called hello-world. Let’s open the index.md file and we should see at the top the metadata for this post.

---
title: Hello World
date: "2015-05-01T22:12:03.284Z"
description: "Hello World"
---

We can add any arbitrary field here but we’ll have to make sure to have the graphQL query return them. Let’s add a few tags called Beginning and World.

---
title: Hello World
date: "2015-05-01T22:12:03.284Z"
description: "Hello World"
tags: Beginning, World
---

We’ll list them as comma-separated words and we should be good to go. At this point we know that we are providing additional data for this post, how do we access it ?

3. Update the GraphQL page query

The home page of the blog is used as an index for the articles. We want to be able to show tags related to the article right from the home. In order to do so we need to return tags from the GraphQL query in src/pages/index.js. At the bottom of this file we can see the query that feeds the data to this page. If you inspect the query string you’ll notice a section named frontmatter, in it a list of all the metadata fields we are returning with this query. It’s missing one field though…tags! So go ahead and add it to the list so it’ll be available for every post in our component.

It should look something like this:

export const pageQuery = graphql`
  query {
    site {
      siteMetadata {
        title
      }
    }
    allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
      edges {
        node {
          excerpt
          fields {
            slug
          }
          frontmatter {
            date(formatString: "MMMM DD, YYYY")
            title
            description
            tags
          }
        }
      }
    }
  }
`

4. Use the additional metadata to show off your post tags

Now that the tags metadata is available we can show it next to the post date. We are going to update how each post is listed by modifying the component returned by the map function.

{posts.map(({ node }) => {
  const title = node.frontmatter.title || node.fields.slug
  const tags = node.frontmatter.tags
  return (
    <article key={node.fields.slug}>
      <header>
        <h3
          style={{
            marginBottom: rhythm(1 / 4),
          }}
        >
          <Link style={{ boxShadow: `none` }} to={node.fields.slug}>
            {title}
          </Link>
        </h3>
        <small>{node.frontmatter.date}</small>
        {tags && tags.length > 0 ? ` - ` : ``}
        {tags && tags.split(", ").map(t => (
          <span key={`${title}-${t}`}>{t}</span>
        ))}
      </header>
      <section>
        <p
          dangerouslySetInnerHTML={{
            __html: node.frontmatter.description || node.excerpt,
          }}
        />
      </section>
    </article>
  )
})}

Notice we are leveraging the && operator to prevent us from calling .split or length on a null value. This is where the choice of a separator is important, since the field is given to as as a string (e.g. "Beginning, World"), we need to transform it into an array of values so we can map over it. That’s what .split is for!

Also we are conditionally adding a simple - separator to add some room between the post date and the tags. Just a small visual detail. Speaking of visual detail, at this point our tags look … not great. Let’s tackle that next!

5. Styling tags

As we’re styling tags I’d like to extract them into their own component, this way we can easily reuse them across our blog. Specifically we also want to show tags in each article page. Let’s go into src/components and create a tags.js file. Here’s what we got:

import React from "react"

const Tags = ({ children }) =>
  children && (
    <ul style={{ marginBottom: 0, marginLeft: 0, display: "inline-flex" }}>
      {children.split(", ").map(t => (
        <li
          key={t}
          style={{
            borderRadius: `4px`,
            border: `1px solid grey`,
            padding: `2px 6px`,
            marginRight: `5px`,
            fontSize: `80%`,
            backgroundColor: "#007acc",
            color: "white",
            listStyle: "none",
          }}
        >
          {t}
        </li>
      ))}
    </ul>
  )

export default Tags

I’m not exactly a designer here but that’ll do it! I’ll let you refactor this into a TypeScript component or add propTypes as a task.

We can now safely remove the additional code we had before in the index.js file, which should now look like this:

// Other imports
import Tags from "../components/tags"

// Some other code

{posts.map(({ node }) => {
  const title = node.frontmatter.title || node.fields.slug
  const tags = node.frontmatter.tags
  return (
    <article key={node.fields.slug}>
      <header>
        <h3
          style={{
            marginBottom: rhythm(1 / 4),
          }}
        >
          <Link style={{ boxShadow: `none` }} to={node.fields.slug}>
            {title}
          </Link>
        </h3>
        <small>{node.frontmatter.date}</small>
        {tags && tags.length > 0 ? ` - ` : ``}
        <Tags>{tags}</Tags>
      </header>
      <section>
        <p
          dangerouslySetInnerHTML={{
            __html: node.frontmatter.description || node.excerpt,
          }}
        />
      </section>
    </article>
  )
})}

Look at your homepage and 🎉 !

A blog post with tags

Not too bad, next time we will make sure that clicking on a Tag will redirect us to the list of all posts with that tag. Stay tuned!