Implementing "related posts" functionality in 11ty
My portfolio has project pages for my personal projects. These contain a mini write up describing what my project is about, typically with a link to said work, alongside information like technologies and languages used. This works nicely, but I wanted a little header at the end showing all the blog posts that relate to the project.
Every post and project in my custom 11ty template has a tags field in their frontmatter: an array of strings containing words that relate to whatever the post or project is about.
I tried searching for this, and couldn't find resources that suited my needs, so I decided to roll my own for fun!
The solution
I knew I'd need to start with a filter. In 11ty, filters are special functions you can call from your templates. They're the main way your template communicates with 11ty's JavaScript. In my project-post template, I added the following:
<section class="related-posts">
{% set relatedPosts = (tags | relatedPosts(collections)) %}
{%if tags and relatedPosts.length > 1 %}
<div>
<h1>More about this...</h1>
<ul>
{% for post in relatedPosts %}
{% include "layouts/article-exerpt.html" %}
{% endfor %}
</ul>
</div>
{%endif%}
</section>
I defined the relatedPosts variable as the result of calling the relatedPosts filter. For the uninitiated, this is nunjucks, which is why the syntax looks a little... Unconventional. Before the vertical slash I'm passing the tags field into the relatedPosts filter, and when I call the relatedPosts filter, I'm passing in the collections variable, which contains all the different tags I've set across my posts and projects. The layout for the post excerpts remains the same thanks to the article-exerpt template I use on the home page!
Now for the filter's code:
eleventyConfig.addFilter("relatedPosts", function(tags, collections) {
let updatedTags = [...tags];
updatedTags.shift(); // Remove the base collection like "blog" or "projects"
let relatedPosts = [];
updatedTags.forEach((tag) => {
if (!collections[tag])
return;
relatedPosts = collections[tag].splice(0, 4);
})
relatedPosts.shift(); // Remove the first match, which will always be the current page's
return relatedPosts;
})
I remove the first tag, as thats usually just what collection the content belongs to (e.g. posts or projects). Then, I go through all the tags of the content calling the filter, and I check if there's a matching "collection", if there is, I get the top 4 posts in that collection. Finally, I remove the first related post, as that is always the post that's calling the filter in the first place!
And the result? A related posts list at the end of my projects! Hope this helps anyone.