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, a self-employed accessibility specialist/advocate, front-end developer, and inclusive designer, located in Norway.
I help companies build accessibile and inclusive products, through accessibility reviews/audits, training and advisory sessions, and also provide front-end consulting.
You might have come across my photorealistic CSS drawings, my work around dataviz accessibility, or my bird photography. To stay up-to-date with my latest writing, you can follow me on mastodon or subscribe to my RSS feed.
💌 Have a freelance project for me or want to book me for a talk?
Contact me through collab@fossheim.io.
Similar posts
Sunday, 15. December 2019
Splitting text into individual characters with React
Wednesday, 18. December 2019