Creating a Content Recommendation Plugin in 11ty
Takes approximately 7 mins to read
Table of contents
In this post, we’re going to create a plugin in Eleventy (11ty) that intelligently analyses blog post tags to suggest related articles to readers. This type of recommendation engine enhances user engagement, increases page views, and offers a tailored reading experience by presenting relevant content suggestions.
Organise your posts with tags
For your content pieces (like blog posts), organise your tags within your frontmatter with associated keywords.
---
title: "My Blog Post"
tags:
- blog post
- tech
- javascript
- technical
---
Create the Plugin
Create a separate file, contentRecommendation.js
, for the implementation of the plugin. It’s good practice to house plugins separately, rather than implementing them directly within your .eleventy.js
file. Personally, I store my plugins in ./config/plugins
.
module.exports = function (eleventyConfig) {
eleventyConfig.addCollection("relatedPosts", function (collection) {
return collection
.getAll()
.filter((item) => !item.data.draft)
.map((post) => {
let related = [];
if (post.data.tags) {
post.data.tags.forEach((tag) => {
collection.getFilteredByTag(tag).forEach((item) => {
if (
item.url !== post.url &&
!related.includes(item) &&
!item.data.draft
) {
related.push(item);
}
});
});
}
// Remove duplicates and limit to a specific number of related posts, for instance, 3
related = [...new Set(related)].slice(0, 3);
post.data.related = related;
return post;
});
});
};
Let’s break down what’s going on here.
Export the module
module.exports = function(eleventyConfig) {
...
};
Here, we’re defining a module that exports a function. This function accepts a single argument, eleventyConfig
, representing Eleventy’s configuration object. You can use this object to access various Eleventy APIs, such as adding collections, filters, and more.
Add a collection
eleventyConfig.addCollection("relatedPosts", function(collection) {
...
});
We’re using addCollection
to define a new collection named “relatedPosts”. In Eleventy, collections let you group pieces of content together. The callback function for addCollection
supplies a collection
object, which offers various methods to access your content.
Filter out draft posts
return collection
.getAll()
.filter((item) => !item.data.draft)
.map((post) => {
...
});
Here, two main operations are carried out on the collection:
collection.getAll()
: This retrieves all items from the collection, with each item representing a piece of content, such as a blog post.filter(item => !item.data.draft)
: We utilise thefilter
method to exclude items (or posts) that have the frontmatter propertydraft
set totrue
, ensuring that drafts aren’t considered for recommendations.
---
title: 'My Post'
draft: true
---
Using map
, we iterate over each post (excluding drafts). For each post, we identify related content and return an amended post with the associated content attached.
Define an empty array for related posts
let related = [];
This array will store the related posts for the current post
being processed.
Check for tags and find related posts
if (post.data.tags) {
post.data.tags.forEach((tag) => {
collection.getFilteredByTag(tag).forEach((item) => {
if (
item.url !== post.url &&
!related.includes(item) &&
!item.data.draft
) {
related.push(item);
}
});
});
}
- If the current post has tags (
post.data.tags
), we iterate over each tag. - For each tag, we find all items associated with that tag using collection.
getFilteredByTag(tag)
. - For each item found, we perform some checks:
- Ensure the item’s URL is not the same as the current post’s URL. This ensures we don’t recommend the post to itself.
- Check that this item is not already in the
related
array (avoid duplicates). - Ensure this item is not a draft (
!item.data.draft
).
- If all conditions are met, the item is pushed to the
related
array.
Limiting and removing duplicate posts
related = [...new Set(related)].slice(0, 2);
[...new Set(related)]
: To remove duplicates from an array, convert the array to aSet
and back to an array. Any duplicates are automatically eliminated this way.slice(0, 2)
: This limits related posts array to only 3 items at any one time - adjust as necessary!
Attach related posts to the current post
post.data.related = related;
We’re adding the related posts to the data
object of the current post under a new property named related
.
Return the modified post
return post;
Finally, we employ the map
function to return our amended post (with its associated content) and incorporate it into the new “relatedPosts” collection.
Integrate plugin
Now that we’ve implemented our plugin, we aim to integrate it into our 11ty site. Navigate to your .eleventy.js
configuration and incorporate the plugin.
const contentRecommendation = require("./config/plugins/contentRecommendation");
module.exports = function (eleventyConfig) {
eleventyConfig.addPlugin(contentRecommendation);
};
Display related posts in template files
Now that we’ve integrated our plugin, we can display the related posts within our template files. For this example, I’m utilising Nunjucks.
Within my _layouts/post.njk
template, I wish to present the related posts at the conclusion of all posts.
{% if related %}
<aside>
<h2>You might also like:</h2>
<ul class="grid mt-l-xl" role="list">
{% for relatedPost in related %}
<li class="card flow overflow-hidden">
<a href="{{ relatedPost.url }}">{{ relatedPost.data.title }}</a>
</li>
{% endfor %}
</ul>
</aside>
{% endif %}
Now if you scroll to the end of this post, you’ll observe a section titled “You might also like:”. This allows readers to explore additional posts.
Essentially, this plugin creates a collection named “relatedPosts”, where each post includes an additional related
property in its data
object that holds an array of posts which share tags with its current post but do not overlap, thus eliminating duplicate posts and drafts.