YS Learns..

daily tinkers with software, infrastructure, coffee, running and photography

Adding #hashtagging to Gatsby

#gatsby, #react, #hashtag

Following the previous post on bootstrapping a bare basics Gatsby blog, let’s explore adding hashtag functionality to make it feel less static. You can add any metadata you like to the markdown header bits of your posts and query them later using GraphQL to create a sense of data-driven dynamics. In this post, we’ll look at using this to #hashtag-enable your blog.

alt text
Hashtags. Photo by Jan Baborák on Unsplash.

Step 1. Add tags to individual markdown posts

title: 'Add #hashtagging to Gatsby'
slug: gatsby-tags
tags:- gatsby- react- hashtag

Step 2. Revise the templates to display the list of tags on each post

  • The files are templates/blog-post.js and optionally, pages/index.js
  • First, extract the tags field in the GraphQL query
    query BlogPostBySlug($slug: String!) {
        markdownRemark(fields: { slug: { eq: $slug } }) {
            ...
            frontmatter {
                title
                tags            }
        }
    }
  • Then format it nicely however you like it..
    const tags = post.frontmatter.tags || []
    const tagsList = tags.map(tag => (
        <Link key={tag} to={`/tags/${tag}`}>#{tag}</Link>
    )).reduce((prev, curr) => [prev, ', ', curr])
  • and insert into your template
    <article>
        <header>
            <h2>{post.frontmatter.title}</h2>
            <p>{date}</p>
            <p>{tagsList}</p>        </header>
        <section ...>
    </article>

Step 3. Add a tags listing page to display all tags and their number of posts

  • Create this file at pages/tags.js
    import React from "react"
    import { Link, graphql } from "gatsby"
    import Layout from "../components/layout"
    import SEO from "../components/seo"

    export default ({ data, location }) => {
        const { title } = data.site.siteMetadata
        const tags = data.allMarkdownRemark.group.map(group =>
            const tag = group.fieldValue
            return (
                <li key={tag}>
                    <Link key={tag} to={`/tags/${tag}/`}>
                        #{tag}
                    </Link>
                    {` (${group.totalCount})`}
                </li>
            )
        )

        return (
            <Layout location={location} title={title}>
                <SEO title="Tags" />
                <div>
                    <h2>Tags</h2>
                    <ul>{tags}</ul>
                </div>
            </Layout>
        )
    }

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

Step 4. Define what the per-tag post listing page looks like

  • Create this file at templates/tags.js
    import React from "react"
    import { Link, graphql } from "gatsby"
    import Layout from "../components/layout"
    import SEO from "../components/seo"

    export default ({ pageContext, data, location }) => {
        const { title } = data.site.siteMetadata
        const pageTitle = `#${pageContext.tag} posts`
        const { edges, totalCount } = data.allMarkdownRemark
        const posts = edges.map(
            ({ node: { frontmatter: { title, slug } } }) =>
                <li key={slug}>
                    <Link to={`/${slug}`}>{title}</Link>
                </li>
        )
        return (
            <Layout location={location} title={title}>
                <SEO title={pageTitle} />
                <h2>{pageTitle} ({totalCount})</h2>
                <ul>{posts}</ul>
            </Layout>
        )
    }

    export const pageQuery = graphql`
        query($tag: String) {
            site {
                siteMetadata {
                    title
                }
            }
            allMarkdownRemark(
                filter: {
                    frontmatter: { tags: { in: [$tag] } }
                }
                sort: { fields: [fields___slug], order: DESC }
            ) {
                totalCount
                edges {
                    node {
                        frontmatter {
                            title
                            slug
                        }
                    }
                }
            }
        }
    `

5. Generate the per-tag pages to display all posts under each tag

  • Edit gatsby-node.js
  • First, collate the tags in the existing logic traversing pages
    let tags = []    const posts = result.data.allMarkdownRemark.edges
    posts.forEach((post, index) => {
        if (post.node.frontmatter.tags) {            tags = tags.concat(post.node.frontmatter.tags)        }        ...
    })
  • Then, create the tag pages after the post pages
    const tagTemplate = path.resolve("src/templates/tags.js")
    tags.filter((elem, pos, arr) => arr.indexOf(elem) === pos)
        .forEach(tag => {
            createPage({
                path: `/tags/${tag}/`,
                component: tagTemplate,
                context: { tag },
            })
        })

Let there be #tags

Your blog is now organised by tags while remaining blazing fast. Gatsby pre-generates all pages during the build process so viewing the tags list page or listing posts per tag load instantly. Note that this approach relies on having a single tags list on each post defined in the markdown header bits. Using dynamic #tags in content involves a bit more work if that’s what you’re going for.


Yong Sheng Tan

Written by Yong Sheng Tan from sunny Singapore

Twitter  ·  GitHub  ·  LinkedIn

All thoughts, opinions, code and other media are expressed here in a personal capacity and do not represent any other entities or persons