How do you sort site.tags by post count in Jekyll?

It's also possible to do this without plugins, which means that it will work on GitHub Pages.

I'm already doing something similar (without plugins as well) on my blog, where I'm displaying a list of tags with post counts, alphabetically sorted. The source code is here.

It's not much effort to modify this so that it sorts by post count:

{% capture tags %}
  {% for tag in site.tags %}
    {{ tag[1].size | plus: 1000 }}#{{ tag[0] }}#{{ tag[1].size }}
  {% endfor %}
{% endcapture %}
{% assign sortedtags = tags | split:' ' | sort %}
{% for tag in sortedtags reversed %}
    {% assign tagitems = tag | split: '#' %}
    <li><a href="/tags/#{{ tagitems[1] }}">{{ tagitems[1] }} ({{ tagitems[2] }})</a></li>
{% endfor %}

I guess there's some explanation necessary:

tag[0] is the name of the tag.
tag[1] is an array with the posts for the tag, so tag[1].size is the post count.

  1. Basically, we would need to capture something like tag[1].size#tag[0], which would result in a string like this:

    3#TagWithThreePosts 1#TagWithOnePost 2#TagWithTwoPosts
    
  2. Then, in the {% assign sortedtags = ... line, we're splitting that again and sorting it, so the result is a sorted array of strings:

    • 1#TagWithOnePost
    • 2#TagWithTwoPosts
    • 3#TagWithThreePosts
  3. In the final loop, we loop this in reverse (=descending) order, split by # to get the tag name and the post count, and display the link.

The only problems are tags with 10 and more posts.
Since we're sorting strings, the result of step 2 would look like this:

  • 1#TagWithOnePost
  • 10#TagWithTenPostsWRONG ORDER, because it's a string!
  • 2#TagWithTwoPosts
  • 3#TagWithThreePosts

To fix this, I'm adding 1000 to the post count for the purpose of sorting. So 1#... and 10#... become 1001#... and 1010#..., and they are ordered properly.

I still want to display the actual post number (without 1000 added), so I'm appending it as the third item in the {% capture tags %} part:

{{ tag[1].size | plus: 1000 }}#{{ tag[0] }}#{{ tag[1].size }}

By the way, I'm linking to a tag page (/tags/#blah, e.g. all posts for all tags on a single page) which I implemented in a similar fashion as well, described here.


Well actually, from what I'm currently reading, the Tag plugins in Jekyll should be used just like a tag and not like a variable. So in that case, you should indeed use this in your template :

{% popular_tags %}

But it's the behaviour of your class that seems to be wrong. It should not return a variable/hash, it should return the HTML code that will be displayed in stead of the popular_tags tag.

For instance, here's something you could be doing :

module Jekyll
  class PopularTags < Liquid::Tag

    def initialize(tag_name, text, tokens)
      super
    end

    def render(context)
      tags = context.registers[:site].tags

      html = "<ul>"
      sorted = tags.sort_by { |t,posts| posts.count }
      sorted.each do |t, posts|
        html << "<li>TAG: #{t} (#{posts.count})</li>"
      end
      html << "</ul>"

      html
    end
  end
end

Liquid::Template.register_tag('popular_tags', Jekyll::PopularTags)

Hope this helps. I just tried it and it's working as intended. If you want to display the most used tags first, just change the sort_by line, and use -posts.count instead of posts.count.

You can have a look at this other plugin source code, might help you.

Tags:

Ruby

Jekyll