Enea Xharja Logo

Add Tags & Categories to Gatsby MDX blog

Tags and categories are an important section of a website, since they provide an easy way for visitors to browse content on your platform.

To add tags to your Gatsby MDX blog posts, you need to have your site set up to convert MDX files into blog posts (I wrote about it here).

Add tags to your MDX files

You can add tags in the frontmatter of your MDX file:

mdx
1---
2slug: '/add-tags-and-categories-to-gatsby-mdx-blog'
3date: '2020-08-23'
4title: '🕺 Add Tags & Categories to Gatsby MDX blog'
5tags: ['js', 'react', 'gatsby']
6---

The frontmatter is the top area of your Markdown or MDX file that is surrounded by dashes. It includes the title of your post, a date and any other field that is a string, number, or array. Considering that posts can have multiple tags, I have defined the above tag field as an array.

Write a query to get all tags for your posts

Now that the tags fields are available in the data layer, we can use graphql to query it. Try running the following query, which groups posts by tags, locally in your GraphiQL (http://localhost:8000/___graphql):

graphql
1{
2 tagsGroup: allMdx(limit: 2000) {
3 group(field: frontmatter___tags) {
4 fieldValue
5 }
6 }
7}

Then, modify your gatsby-node.js file with the above query:

js
1const path = require('path');
2const _ = require('lodash');
3
4exports.createPages = async ({ actions, graphql, reporter }) => {
5 const { createPage } = actions;
6
7 const tagTemplate = path.resolve('src/templates/tagTemplate.js');
8
9 const result = await graphql(`
10 {
11 tagsGroup: allMdx(limit: 2000) {
12 group(field: frontmatter___tags) {
13 fieldValue
14 }
15 }
16 }
17 `);
18
19 // ...
20
21 // Extract tag data from query
22 const tags = result.data.tagsGroup.group;
23 // Make tag pages
24 tags.forEach(tag => {
25 createPage({
26 path: `/tags/${_.kebabCase(tag.fieldValue)}/`,
27 component: tagTemplate,
28 context: {
29 tag: tag.fieldValue,
30 },
31 });
32 });
33};

For reference, here is my entire gatsby-node.js file.

Make a tags page template

As you can see on the gatsby-node.js file, we need a tag page template in order to generate individual pages for the tags in your posts.

Let's create a tag template for /tags/{tag}:

js
1import React from 'react';
2import { Link, graphql } from 'gatsby';
3
4import Layout from '../components/layout';
5import SEO from '../components/seo';
6
7export const query = graphql`
8 query($tag: String) {
9 allMdx(
10 limit: 2000
11 sort: { fields: [frontmatter___date], order: DESC }
12 filter: { frontmatter: { tags: { in: [$tag] } } }
13 ) {
14 totalCount
15 edges {
16 node {
17 excerpt
18 frontmatter {
19 title
20 slug
21 date(formatString: "MMMM DD, YYYY")
22 tags
23 }
24 }
25 }
26 }
27 }
28`;
29
30const Tags = ({ pageContext, data }) => {
31 const { tag } = pageContext;
32 const { edges, totalCount } = data.allMdx;
33 const tagHeader = `${totalCount} post${
34 totalCount === 1 ? '' : 's'
35 } tagged with "${tag}"`;
36
37 return (
38 <Layout>
39 <SEO title={`"${tag}" tag`} />
40
41 <h1>{tagHeader}</h1>
42
43 <div>
44 <Link to="/tags">View all tags</Link>
45
46 {edges.map(({ node }) => {
47 const { excerpt } = node;
48 const { slug, title, date, tags } = node.frontmatter;
49
50 return (
51 <div key={slug}>
52 <Link to={`/blog${slug}`}>
53 <h3>{title}</h3>
54 </Link>
55
56 <p>
57 {date}
58 <span>Tag: </span>
59 {tags.map(tag => (
60 <Link
61 key={tag.toLowerCase()}
62 to={`/tags/${tag.toLowerCase()}`}
63 >
64 {tag}
65 </Link>
66 ))}
67 </p>
68
69 <p>{excerpt}</p>
70 </div>
71 );
72 })}
73 </div>
74 </Layout>
75 );
76};
77
78export default Tags;

Make a tags index page

Finally, once we've generated pages for every tag, it's time to create a tags index page. This page will render a list of all tags (src/pages/tags.js), followed by the number of posts with that particular tag.

js
1import React from 'react';
2import { Link, graphql } from 'gatsby';
3import kebabCase from 'lodash/kebabCase';
4
5import Layout from '../components/layout';
6import SEO from '../components/seo';
7
8export const query = graphql`
9 query {
10 allMdx(limit: 2000) {
11 group(field: frontmatter___tags) {
12 fieldValue
13 totalCount
14 }
15 totalCount
16 }
17 }
18`;
19
20const TagsPage = ({
21 data: {
22 allMdx: { group, totalCount },
23 },
24}) => (
25 <Layout>
26 <SEO title="Tags" />
27
28 <div>
29 <h1>Tags</h1>
30 <p>
31 {totalCount} posts are listed in {group.length} categories
32 </p>
33 {group.map(({ fieldValue, totalCount }) => (
34 <Link key={fieldValue} to={`/tags/${kebabCase(fieldValue)}`}>
35 {fieldValue} ({totalCount})
36 </Link>
37 ))}
38 </div>
39 </Layout>
40);
41
42export default TagsPage;

You can have a look at how tags work in Gatsby directly from my personal website repo on GitHub.