Adding #hashtagging to Gatsby
Mar 16th 2020, 8:01am
3 min read
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.
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.