Adding tags to your Gatsby blog entries
This blog now has tags. It was super easy to add these, and I didn’t need any additional NPM packages or 3rd party services.
TL;DR; You can pre-generate all your tag-index pages at build-time using Gatsby’s createPages
API and some simple javascript.
Example
Assuming you have a tags
array field in your headless CMS’s data-model (what, you don’t use a headless CMS?? Here are a ton of options), you can create groupings of all of your blog entries by tag using this simple example in your gatsby-node.js
. It’s basically the same as the gatsby-starter-blog example, but with the highlighted additions for tag-indexing.
Creating the entry and tag pages
// Helper to build the tag index
const indexEntry = (index, tag, entry) => {
if (!index[tag]) {
index[tag] = [];
}
index[tag].push(entry);
};
exports.createPages = ({ graphql, actions: { createPage } }) => {
const tagIndexTemplate = path.resolve('./src/templates/tagIndex.js');
// Find all of my blog entries with tags
return graphql(`
{
allContentfulEntries {
edges {
node {
slug // the entry slug
tags // the tags array
} }
}
}
`).then(result => {
const entries = result.data.allContentfulEntries.edges;
const tagIndex = {};
// Iterate over all entries, building the tag index as we create the entry pages
entries.forEach((entry, index) => {
// Create the actual entry page
createPage({
path: `/entry/${entry.node.slug}`,
component: entryTemplate,
context: {
slug: entry.node.slug,
},
});
// Add this page to the tag index entry.node.tags.forEach(tag => { indexEntry(tagIndex, tag, entry.node); }); });
// Build the tag pages from the tag index
Object.keys(tagIndex).forEach(tag => {
createPage({ path: `/tag/${tag}`, component: tagIndexTemplate, // Pass the tag and the list of slugs in the index to the page template // The page template will use these to render the list of entries for the tag context: { tag: tag, pages: tagIndex[tag], }, }); });
});
};
According to Gatsby createPage
docs,
Context data for this page. Passed as props to the component
this.props.pageContext
as well as to the graphql query as graphql arguments.
So now that we’ve passed pages
as context to the tagIndex
page template, we can do this…
Rendering a tag page
const TagIndexPage = ({
// passed in from createPage
pageContext: { tag, pages },
// passed in from pageQuery results
data: {
allContentfulTil: { edges },
},
}) => {
// Helper which turns the entry edge array into a map of slug -> entry
// We'll use this below to look up all the metadata for the entry
const allTils = flattenTils(edges);
return (
<Layout>
<header><h2>{tag}</h2></header>
{pages.map(({ slug }) =>
<Link to={`/entry/${slug}`}>{allTils[slug].title}</a>
)}
</Layout>
);
};
flattenTils
helper
Here’s the helper used to convert an array of edges returned from the Gatsby pagequery to a map. I use this technique quite a bit to create lookups for Gatsby data arrays.
import { mergeAll, map, prop, compose } from 'ramda';
// [{ node: { slug: "entry-1", title: "entry one", ... } }] => { "entry-1": { title: "entry one", ... } }
export default compose(
// [{ slug1: entry1 }, { slug2: entry2 }] => { slug1: entry1, slug2: entry2 }
mergeAll,
// [entry] => [{ slug: entry }]
map(node => ({
[node.slug]: node,
})),
// [{ node: { ... } }, { node: { ... } }] => [{ ... }. { ... }]
map(prop('node'))
);