At the top of posts on this site, you can see the tags for each post. This was a somewhat “out-of-the-box” feature of the Jekyll theme that I chose to use and I always liked the look of them, but it bugged me that they were just text without a link to anywhere. It made sense to me that they would be a link to a page that included the posts that also used this tag.
Simple, right?
New Layout
First, we need to make “tag” pages. This can be configured in the Jekyll config yaml file,
collections:
tags:
output: true
permalink: tags/:path/
This will create a file structure of /tags/
with every markdown file that’s in the _tags
directory of my source. Great, but I don’t have any files in there yet. Let’s manually create one,
say front-end.md
, and come back to this later. We now will have a single page at /tags/front-end
.
Content
For content on these tag pages, we can assume we’re given a tag-name
in the front-matter. This allows us to create a header with the given tag name.
<h1>{{ page.tag-name }}</h1>
The following posts mention {{ page.tag-name }}
Let’s add a list of all the pages that match this tag. For this we will have to use the variables provided to us from Jekyll.
<ul class="tags-list">
{% for post in site.posts %}
{% if post.tags contains page.tag-name %}
<li><a href="{{ post.url }}">{{ post.title }}</a></li>
{% endif %}
{% endfor %}
</ul>
Performance (aside)
The above code snippet really bothered me for a while. As a full-stack/backend engineer, I first
approached this part the way one would for a dynamic site - that is one that is running some type
of web server. Assuming the page is given a tag name, like front-end
, it should be able to
“look up” all the pages that have that tag name. In Rails, this would
look like,
Pages.where(tags: Tag.find_by(name: given_tag_name))
The problem here is that we’re not working with a dynamic site, we’re working with Jekyll, a static site generator (duh). This means that the concept of “looking up” the tags is pushed to compile/generation time. What this really means is that we have to work with the variables that Jekyll gives us.
Since they don’t provide a pages per tag variable, we’ll have to iterate over the pages and select
only those that use the given tag. If we have n
pages, and t
tags, that would be O(n * t)
to iterate over all pages once per tag. That seems really nasty to me, but we have to consider a few
things.
t
is likely going to be far smaller thann
overall, which would make thisO(n)
as the number of pages grows.This is done ahead of time, not during a page request as with a dynamic site.
If Jekyll were to give us a data structure of pages for a given tag, it could be done in O(n + t)
by iterating over the pages once and pushing a unique identifier into a hash that is keyed off the
tag, then iterating over the tags to generate the pages.
{
"front-end": [1, 2],
"css": [2, 3]
}
This would be my “typical” answer to an interview question on the manner. A
hash (dictonary, map) is perfect for this
type of work because it allows us to create O(1)
lookups by tag and remove unnecessary extra
traversals through data.
Page per Tag (Plugins)
Back on topic, we now have a layout that will display the pages that contain a given tag, but we would have to manually generate a markdown file for each tag we use. If we forget one, the system breaks.
That doesn’t jive with the easy-breezy automate everything lifestyle I’m going for. A quick Google
to see what others had come up with revealed a
delightful article written
by Anna Ślimak that had a novel solution, use Jekyll plugins.
This takes advantage of the underlying Ruby code of Jekyll to extend the behavior. In this case, a
plugin that is run after each post is generated during build that can inspect the file system and
see if we need to write a tag markdown file. Great! We just put this file into our _plugins
directory and poof, we’re done…
Wait, why are they not running?
Turns out that GitHub Pages does not support plugins. This makes total sense as if they did, anyone could run arbitrary Ruby code on their deploy servers. The gem that I use therefore does not run plugins locally as well. Unfortunately, it doesn’t SAY that plugins aren’t run, so it took me a few minutes to figure that one out.
Wrapping Up
It was then that I remember that I can write Ruby myself. I wrote a small script that I must run before deploying to GitHub Pages. This isn’t perfect, but it works just fine. The plugin would have had to be run by serving or building the site before commit anyway, so this seems like a decent trade off.
With that, we now have pages for each unique tag used in the posts that show every article using that tag. A little quick update to the post layout and css and we’re good to go.
What I Learned
I learned some about the workings of Jekyll, including plugins. I also thought through some amusing static site generation issues.
What Comes Next
I would love to do more with the tags. Maybe a little javascript component that shows a preview of the tags.