Creating tag index pages with Gatsby

Oct 08, 2023

I've been giving Gatsby a trial run in this new version of my site. When using GitHub pages, one of the things I'd always longed for was dynamic tag "index" pages for my blog posts.

Tag post list template

The first step is to create a template to render a list of posts for a given tag. The component for this is quite simple:

const TagPostListTemplate = (props: TagTemplateProps) => { const posts = props.data.allMarkdownRemark.edges; return ( <Layout> <ul> {posts.map(({ node }) => ( <BlogPostPreview key={node.id} slug={node.frontmatter.slug} title={node.frontmatter.title} tags={node.frontmatter.tags} excerpt={node.excerpt} /> ))} </ul> </Layout> ); };

The props will be populated from a GraphQL query, and to make things easier I'm using the same BlogPostPreview component that I use on the homepage.

The GraphQL query is quite simple, too. My tags are stored in the frontmatter of the markdown pages, and we can use a filter clause to check that the $tag query variable is in the tags listed against each post.

export const query = graphql` query($tag: String!) { allMarkdownRemark( sort: { frontmatter: { date: DESC } }, filter: { frontmatter: { tags: { in: [$tag] } } } ) { edges { node { excerpt(format: HTML) id frontmatter { title, slug, tags } } } } } `;

Instantiating the pages

We now have a template capable of listing all posts matching a given tag, so the next step is to use Gatsby's page generation methods to create an instance of this template for each of the extant tags.

This can be handled within gatsby-node.js in a createPages function (you can read the docs about this here). The first step is to query all existing tags via GraphQL.

exports.createPages = async ({ graphql, actions }) => { const { createPage } = actions; const result = await graphql(` { allMarkdownRemark { edges { node { frontmatter { tags } } } } } `); if (result.errors) { reporter.panicOnBuild(`Error while running GraphQL query.`); return; } const posts = result.data.allMarkdownRemark.edges;

We now have access to all posts in the system, and all tags on those posts. Let's create an array of unique tags.

const tags = Array.from( new Set( result.data.allMarkdownRemark.edges.flatMap( edge => edge.node.frontmatter.tags ) ) );

Now it's as simple as using the Gatsby createPage action, and using the template we created earlier as reference.

const tagTemplate = path.resolve(`src/templates/TagPostListTemplate.tsx`); tags.forEach(tag => { createPage({ path: `/tags/${_.kebabCase(tag)}/`, component: tagTemplate, context: { tag, }, }); });

Finishing up

After running a Gatsby build, a number of /tags/ pages will be created for each tag in the system. Try clicking on one of the tags against this blog post to see how the filtering works.

Exercises for the reader

  • Link to the /tags/ index page when tags are clicked
  • Add a way to clear selected tag
  • Add a highlight to the currently selected tag (I used React Context for this)

Like my blog? Try my game!

Creature Chess is a lofi chess battler

It's free, you can play it from your browser, and a game takes 10 minutes

Play Creature Chess

Find my other work on Github @jameskmonger