Adding categories to Eleventy

I've decided to put a bit more love into this blog, I've neglected it since the new year. As part of that, I wanted to make a few more changes - namely, I wanted some better navigation to allow me to write different types of content. So, I've added a little category list to the site to allow people to search by different tags.

Organising posts

First of all, to identify blog posts, I use a single tag: posts. I use an 11tydata.json file in my posts directory that looks like this to ensure every post is automatically tagged correctly:

{
"tags": [
"posts"
],
"permalink": "post/adding-categories-to-eleventy/",
"layout": "post.njk"
}

I also have a draft tag, that I use to un-publish posts that I'm working on without needing to keep WIP on one machine. I'll assume that any other tag that an item in the posts collection has is it's category, and that a post can have multiple categories.

Getting the category list

So, to generate a list of categories and the number of posts in each category, I've added a simple custom collection to my site, called categories. Here's the code:

eleventyConfig.addCollection('categories', (collectionApi) => {
const posts = collectionApi
.getFilteredByTag("posts")
.filter(p => !p.data.tags.includes("draft"));

return posts.reduce((tags, post) => {
post.data.tags.filter(tag => tag !== 'posts').forEach(tag => {
if (!tags[tag]) {
tags[tag] = 0;
}
tags[tag]++;
});
return tags;
}, {"All posts": posts.length})
});

It's fairly simple, even if Javascript's reduce is a callback-headache. All we're doing is getting all of the items in the posts collection, removing anything tagged as a draft post, and then for each tag we're first checking if the tag already exists. If it doesn't exist, we initialise it in our tags object with a count of 0. Then, we increment the tag count by 1. We then also add an extra tag called All posts, which is the total count of the posts object.

The output of this function is an object that looks like this:

{
"All posts": 10,
"frontend": 3,
"backend": 2,
"recipes": 4,
"books": 1
}

Displaying categories

Listing the categories is easy, we just need to use our new collection:

<ul>
{% for category, count in (collections.categories) %}
{% if category == "All posts" %}
<li><a href="{{ '/blog' | url }}">{{ category }} ({{ count }})</a></li>
{% else %}
{% set caturl = ["/blog", "category", category] | join("/") %}
<li><a href="{{ caturl | url }}">{{ category }} ({{ count }})</a></li>
{% endif %}
{% endfor %}
</ul>

To actually display a category, Eleventy has an easy guide for this. We just need a bit of customisation to use our blog layout, filter out tags such as drafts, and the categories themselves, and then set our permalink:

pagination:
data: collections
size: 1
alias: tag
filter:
- draft
- categories
- all
permalink: /blog/category/{{ tag }}/
layout: blog.njk
renderData:
title: Posts Tagged "{{ tag }}"
title: Lewis Dale's Blog
---

{% set taglist = collections[ tag ] %}
{% for post in (taglist | filterDrafts | sortedByDate) %}
{% include "components/blogpost.njk" %}
{% endfor %}

And that's pretty much it! There's probably still some work to be done with paginating the tags once I have enough posts to need it.