Stable list of related posts

I’m working off of the Beg theme, which has a clever feature to include related posts at the bottom of pages. But the current implementation is a little overly-simplistic; it just lists all other posts that share a tag, which is a pretty loose definition of “related” and risks ending up looking pretty bad on pages with many tags or tags with many posts.

I’d like to redefine “related” to be more precise and also stable - that is to say new posts won’t shuffle the links displayed on existing pages. So here’s my plan:

Ordered by date, match the first n posts (n ~= 4) that share two or more tags and are older than the current post.

This should ensure we really only link to fairly relevant posts, and that those posts will be stable over time (assuming posts don’t get re-tagged, but that’s unavoidable).

My problem is this is a bit complex to describe in html/template syntax. I’m pretty sure it can be done, but I’m not sure what the best way is. I’ll post a reply with any progress I make, but I’d appreciate any suggestions. Once I have something working I’ll try to make it reusable and contribute it back to Beg.

Here’s what I have so far:

<h3>Related Posts</h3>
<ul class="post-rels">
    {{ range where (where .Site.Pages.ByDate.Reverse "Type" "post") "Date.Unix" "<" .Date.Unix }}
      {{ if and (lt ($.Scratch.Get "related_matches") $.Site.Params.NumRelatedPosts) (ge (intersect $.Params.tags .Params.tags | len) 2) }}
      <li><a href="{{ .RelPermalink }}">{{ .Title }}</a></li>
      {{ $.Scratch.Add "related_matches" 1 }}
      {{ end }}
    {{ end }}
</ul>

This gets the first NumRelatedPosts posts with two or more shared tags that are older than the current post. But it prints “Related Posts” and the <ul> tag even if there are no matches. I’m not sure if there’s a better solution than moving them inside the range, e.g.

{{ range where (where .Site.Pages.ByDate.Reverse "Type" "post") "Date.Unix" "<" .Date.Unix }}
  {{ if and (lt ($.Scratch.Get "related_matches") $.Site.Params.NumRelatedPosts) (ge (intersect $.Params.tags .Params.tags | len) 2) }}
    {{ if not ($.Scratch.Get "related_matches") }}
    <h3>Related Posts</h3>
    <ul class="post-rels">
    {{end}}
  <li><a href="{{ .RelPermalink }}">{{ .Title }}</a></li>
  {{ $.Scratch.Add "related_matches" 1 }}
  {{ end }}
{{ end }}
{{ if $.Scratch.Get "related_matches" }}
</ul>
{{ end }}

Any thoughts? I noticed the intersect docs describe the same pattern Beg uses; if this seems cleaner I can post a pull request updating the example.

2 Likes

Thanks for sharing!

Yeah, moving in is logical.

I was thinking of implementing related posts on my website too, just coding different things at the moment. It would be very philanthropic to come up with a proper nice related-posts setup and share it with fellow hugo’ers.

By the way, nice Scratch implementation!

For the sake of pulling things together to find more solutions I’ve dropped another example the in the following thread: https://discuss.gohugo.io/t/template-logic-for-related-posts/2947