Lessons

Lesson 5 - Related Posts

rows of sapsucker holes in a tree trunk

There appears to be a bug where the Nunjucks templating language can't be rendered properly in <pre> blocks. Because of this, I will be excerpting the curly bracket-percent sign combos and replacing them with only percent signs. Wherever you see a percent sign in .njk code, add the relevant opening or closing curly bracket. I will also be replacing the double curly brackets with single curly brackets.

By default, the Eleventy base blog comes with pagination between posts. Post 2 can take you to posts 1 and 3, etc.

While that is useful for this site, when building another site I wanted to see a couple randomly-suggested posts that shared 1 or more tags.

I started by referring to this GitHub issue about related posts. I had to fix a few errors that arose from the suggested code.

I also wanted to make two changes:

  1. I didn't want to just see posts that shared all tags, but rather posts that shared any tag
  2. I wanted to randomly add a few posts instead of just getting whatever was first (with a shared tag) in the post order

filters.js

After adjusting for those needs, I had the following in filters.js:

eleventyConfig.addNunjucksFilter("excludeFromCollection", function (collection=[], pageUrl=this.ctx.page.url) {
  return collection.filter(post => post.url !== pageUrl);
});

eleventyConfig.addFilter("filterByTags", function(collection=[], ...requiredTags) {
  return collection.filter(post => {
    return requiredTags.flat().some(tag => post.data.tags.includes(tag));
  });
});

eleventyConfig.addFilter("randomize", function(array) {
  // Create a copy of the array to avoid modifying the original
  let shuffledArray = array.slice();

  // Fisher-Yates shuffle algorithm
  for (let i = shuffledArray.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]];
  }

  return shuffledArray;
});

post.njk

I used this in my post layout. filterTagList comes with the base blog by default, and removes the tags "posts" and "all." head is also a built in.

% set relevantTags = tags | filterTagList %
% set postlist = collections.posts | filterByTags(relevantTags) | excludeFromCollection(page.url) | randomize | head(2) %
% if postlist.length %
<section class="related-posts">
	<h2>related posts</h2>
  % include "postlist.njk" %
</section>
% endif %