Sarah L. Fossheim

sarah@fossheim.io

Developer + Designer

Creating a similar post component with Eleventy

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.

two similar blog posts in big colored containers at the bottom of each of my posts

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:

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:

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;
    }).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

Posted on 2020-04-01


Hi 👋 I'm Sarah, a multidisciplinary developer and designer, specialized in creating complex and data-heavy products that are accessible, ethical and user friendly. I also enjoy making art with CSS. On here, I frequently write about HTML/CSS, React, Python, UX design, accessibility and data visualizations.

If you like my work, consider sharing this post, buying me a coffee, becoming a patron or connecting with me on Twitter. I'm also on Twitch, CodePen, dev.to and GitHub.

Similar post | 2020-05-20

An introduction to accessible data visualizations with D3.js

a11y front-end tutorial D3.js data

Similar post | 2020-03-13

How to create an accordion hover effect with box-shadows

css front-end tutorial