Creating a page for each tag in Jekyll

Jekyll lets you assign tags to posts, but it does not automatically create a browsable page for each tag. A small generator plugin can fill that gap.

Start by adding tags to a post’s front matter. I prefer a YAML array because it stays unambiguous when a tag contains spaces:

layout: post
title: Tags in Jekyll
description: Adding tags to a Jekyll powered website
summary: Adding tags to a Jekyll powered website.
tags: [tags, jekyll]

Next, create _plugins/tags.rb. The generator collects every unique tag and adds one page under /tag/<tag>/:

module Jekyll
  class TagPageGenerator < Generator
    safe true

    def generate(site)
      tags = site.posts.docs.flat_map { |post| post.data['tags'] || [] }.to_set
      tags.each do |tag|
        site.pages << TagPage.new(site, site.source, tag)
      end
    end
  end

  class TagPage < Page
    def initialize(site, base, tag)
      @site = site
      @base = base
      @dir  = File.join('tag', tag)
      @name = 'index.html'

      self.process(@name)
      self.read_yaml(File.join(base, '_layouts'), 'tag.html')
      self.data['tag'] = tag
      self.data['title'] = "Tag: #{tag}"
    end
  end
end

Each generated page reads its markup from _layouts/tag.html. Add this layout:

---
layout: default
---

<h1>{{page.tag}}</h1>
<ul>
  {% for post in site.posts %}
  {% if post.tags contains page.tag %}
  <li><a class="post" href="{{ post.url }}">{{ post.title }}</a></li>
  {% endif %}
  {% endfor %}
</ul>

The page loops through the site’s posts and lists only those containing the current tag. To make those pages discoverable, add tag links to the post layout:

<p>
  Tagged
  {% for tag in page.tags %}
  <a class="post" href="/tag/{{tag}}">#{{tag}}</a>{% unless forloop.last %}, {% endunless %}
  {% endfor %}
</p>

This approach uses a custom plugin, so it will not run in environments that build Jekyll in safe mode without your plugin. If the hosting platform blocks custom plugins, generate the tag pages before deployment or create them explicitly. Otherwise, this keeps the tag archive in sync without maintaining a page by hand for every new label.