When updating my portfolio design, I wanted to create a component that automatically displays similar posts at the bottom of each blog post. Because I couldn't find any tutorials on how to achieve that, I thought it would be a good idea to share my solution.
There's different ways of defining similar posts, but I decided to go for a simple first version: posts are considered similar to each other if they have one category or more in common. For some posts this list can grow quite long, so I limited the component to only show the two posts with the highest number of common categories.
Filtering posts
The main functionality for this feature is added in the Eleventy config file (most likely called .eleventy.js
), where we'll create a custom filter.
eleventyConfig.addLiquidFilter("similarPosts", (collection, path, categories) => {});
The way filters are defined is dependent on which templating language you're using, in my case Liquid. Other variations can be found in the Eleventy filter documentation.
The filter will receive three inputs:
collection
: the collection of posts that should be filteredpath
: the path to the active postcategories
: the categories of the active post
We only want to return posts that have at least one category in common, which I solved this way:
eleventyConfig.addLiquidFilter("similarPosts", (collection, path, categories) => {
return collection.filter((post) => {
return post.data.categories.filter(Set.prototype.has, new Set(categories)).length >= 1;
});
});
This will return a list of posts that have at least one category in common. However, the current post is included in this list as well. We don't want to display the post we're looking at in its own list of similar posts, so it has to be filtered out:
eleventyConfig.addLiquidFilter("similarPosts", (collection, path, categories) => {
return collection.filter((post) => {
return post.data.categories.filter(Set.prototype.has, new Set(categories)).length >= 1
&& post.data.page.inputPath !== path;
});
});
This returns the correct list of similar posts, but not yet sorted by similarity. Using the same way of detecting overlapping categories as above, we can now sort our posts as well:
categories) => {
return collection.filter((post) => {
return post.data.categories.filter(Set.prototype.has, new Set(categories)).length >= 1
&& post.data.page.inputPath !== path;
}).sort((a, b) => {
return b.data.categories.filter(Set.prototype.has, new Set(categories)).length - a.data.categories.filter(Set.prototype.has, new Set(categories)).length;
});
});
Which after some code clean-up looks like this:
const getSimilarCategories = function(categoriesA, categoriesB) {
return categoriesA.filter(Set.prototype.has, new Set(categoriesB)).length;
}
module.exports = function(eleventyConfig) {
... // Other configs
eleventyConfig.addLiquidFilter("similarPosts", function(collection, path, categories){
return collection.filter((post) => {
return getSimilarCategories(post.data.categories, categories) >= 1 && post.data.page.inputPath !== path;
}).sort((a,b) => {
return getSimilarCategories(b.data.categories, categories) - getSimilarCategories(a.data.categories, categories);
});
});
}
Liquid component
Now the only thing left is connecting this to our blog post component. I use Liquid templates, but the principle is the same when using other templating languages.
{% assign similar = collections.sortedPosts | similarPosts: page.inputPath, categories %}
<ul>
{% for post in similar limit: 2 %}
<li>
<a href="{{ post.url }}">{{ post.data.pageTitle }}</a>
</li>
{% endfor %}
</ul>
More sources
Hi! 👋🏻 I'm Sarah, an independent developer, designer and accessibility advocate, located in Oslo, Norway. You might have come across my photorealistic CSS drawings, my work around dataviz accessibility, or EthicalDesign.guide, a directory of learning resources and tools for creating more inclusive products. You can follow me on social media or through my newsletter or RSS feed.
💌 Have a freelance project for me or want to book me for a talk? Contact me through collab@fossheim.io.
If you like my work, consider:
Similar posts
Sunday, 15. December 2019
Splitting text into individual characters with React
Wednesday, 18. December 2019