Managing tags in Jekyll blog easily

There are few options to categorize posts using Jekyll, one can get lost in thinking which to choose and how to use it properly and easily. In our blog, we were using both categories and tags but in a bit chaotic way. I decided to clean up a bit. This is a short story from this journey.

No items found.
Anna Ślimak's avatar
Author:

Anna Ślimak

To categorize your posts in Jekyll you can use categories, tags or both of them. In our blog, we did the latter, but it was a bit chaotic. I decided to clean this up, get rid of categories completely and use tags in a more convenient way.

My goal was to:

  • make it easy to assign tags to posts
  • display tags in post details
  • be able to click any of them and see all posts with this tag
  • display a list of all tags used in the blog

In theory, all of it is pretty easy to do.

To assign tags to a post, just put their names, space separated (or as a YAML array if you prefer this way), in the front matter:

tags: tech jekyll blog ruby
---

To display them, just use post.tags variable:

{% raw %}

{% for tag in post.tags %}
  {% assign tag_slug = tag | slugify: "raw" %}
  <a class="tag-link"
    href={{ site.baseurl | append: "/tags/" | append: tag_slug | append: "/" }}
    rel="category tag">
    #{{ tag }}
  </a>
{% endfor %}

{% endraw %}

To create a tag page, which will display all posts with this tag, add tags collection in the _config.yml:

collections:
  tags:
    output: true
    permalink: tags/:path/

and add a layout:

{% raw %}

---
layout: default
---

<div class="snippets">
  <h1 class="snippets-heading">Articles tagged with "{{ page.tag-name }}"</h1>

  {% for post in site.posts %}
    {% if post.tags contains page.tag-name %}
      {% include snippet.html %}
    {% endif %}
  {% endfor %}
</div>

{% endraw %}

This assumes that you have _tags directory and files for every tag you want to use there (e.g. jekyll.md) with following content:

tag-name: jekyll
---

Maybe it’s easy, but not really convenient - I wanted to be able just to add a tag to the front matter and have it working already, without a need to add an additional file to the _tags directory.

I discovered that there is a mechanism called hooks which I can use to achieve this goal.

I’ve written this simple hook which you can place in the _plugins directory:

Jekyll::Hooks.register :posts, :post_write do |post|
  all_existing_tags = Dir.entries("_tags")
    .map { |t| t.match(/(.*).md/) }
    .compact.map { |m| m[1] }

  tags = post['tags'].reject { |t| t.empty? }
  tags.each do |tag|
    generate_tag_file(tag) if !all_existing_tags.include?(tag)
  end
end

def generate_tag_file(tag)
  File.open("_tags/#{tag}.md", "wb") do |file|
    file << "---\ntag-name: #{tag}\n---\n"
  end
end

And voila! - I don’t need to bother to create tag files manually anymore!

The last thing then - I wanted to display all the tags used in the blog. I could easily display all the tags - using site.tags variable - but I didn’t want to include tags that were used in the past, but are not assigned to any post right now. Also, I thought it would be nice to display post count for each tag.

It turned out that you can write a simple plugin for that:

module AllTagsFilter
  include Liquid::StandardFilters

  def all_tags(posts)
    counts = {}

    posts.each do |post|
      post['tags'].each do |tag|
        if counts[tag]
          counts[tag] += 1
        else
          counts[tag] = 1
        end
      end
    end

    tags = counts.keys
    tags.reject { |t| t.empty? }
      .map { |tag| { 'name' => tag, 'count' => counts[tag] } }
      .sort { |tag1, tag2| tag2['count'] <=> tag1['count'] }
  end
end

Liquid::Template.register_filter(AllTagsFilter)

And use it like that in a template:

{% raw %}

<div class="all-tags">
  All tags:
  <ul>
    {% assign tags = site.posts | all_tags %}
    {% for tag in tags %}
      {% assign tag_slug = tag['name'] | slugify: "raw" %}
      <li>
        <a class="tag-link"
          href={{ site.baseurl | append: "/tags/" | append: tag_slug | append: "/" }}
          rel="category tag">
          #{{ tag['name'] }} ({{ tag['count'] }})
        </a>
      </li>
    {% endfor %}
  </ul>
</div>

{% endraw %}

That’s it! I hope you find it useful.

Want To Discuss
Your Next Project?

Our team is excited to work with you and create something amazing together.

let's talk

let's talk

More articles

We don’t just build software, we understand your business context, challenges, and users needs so everything we create, benefits your business.

thumbnail image for blog post
plus sign
thumbnail image for blog post
plus sign
thumbnail image for blog post
plus sign
Arrow