How do I tag posts in Jekyll? Jekyll tagging made simple.
You added a few posts.
You want to have them organised by tags.
You want to have a URL that lists posts belonging to a tag.
You want to have a specific atom/RSS feed that filters the posts by tag.
Out of the box Jekyll
Jekyll implements tag
and tags
as part of the Post
s’ front matter, docs here.
The sample code, at the time of writing, loops through all tags. And displays all articles belonging to each tag. Useful for a “see all posts by tag, all tags” kind of page. But it leaves it at that.
This doesn’t stop us from extending Jekyll’s default functionality.
Direction
The ideal system would:
- have you add tags to a
tags
variable to the post’s front matter YAML - automatically generate a URL with all posts under that tag at:
/tags/[tag-name]/
- automatically generate an Atom feed URL with all posts under that tag at
/feed/[tag-name].xml
For example, once you add the tag jekyll
to a post, you would:
- see all articles tagged with
jekyll
under/tags/jekyll/
- have an Atom feed containing all posts with tag
jekyll
at/feeds/jekyll.xml
Implementation
Layouts
Let’s start by adding a layout for the:
- “posts by tag” pages, and
- the feed URLs
Place both of these under _layouts
. The directory would look like this:
_layouts
|- feed.xml <- new
|- page.html
|- post.html
|- tags.html <- new
page.html
and post.html
usually come with the theme used.
feed.xml
and tags.html
are new.
tags.html
is a normal page, therefore extending default
layout. Its markup:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
You can notice how the condition:
{% if post.tags contains page.tag-name %} ... {% endif %}
includes only posts containing the tag passed through the post’s tag-name
front matter.
feed.xml
renders an Atom feed. I started by copying the theme’s own atom.xml
. As in the template above, I then added a the same if
condition to filter by tag:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
Layouts - checkpoint
So now we have the layouts. Which on their own do nothing. But you still can test what you’ve done so far.
Create a post, place some dummy content in it, but make sure to include the below in the front matter:
--
...
layout: default
tag-name: tag1 tag2
...
--
Create a directory called _tags
in your root directory. And add a file called tag1.md
with this content:
---
layout: tags
tag-name: tag1
---
Navigate to /tags/tag1/
, and you should see the post you created above in the list. Yay!
Create a directory called _feeds
in your root directory. Add a file called tag1.xml
with this content:
---
layout: feed
tag-name: tag1
---
Navigate to /feeds/tag1.xml
, and again, you should see the post you created above. Yay x2!
Manual creation of a markdown file and an xml feed file, each time you add a tag, is cumbersome. And prone to human error. So let’s have the machine do this for us.
Automating
Use whichever command you usually use to keep Jekyll running. I use jekyll serve
.
For automation purposes, Jekyll offers a plugin system:
Jekyll has a plugin system with hooks that allow you to create custom generated content specific to your site. You can run custom code for your site without having to modify the Jekyll source itself.
Create the plugin file at _plugins/tag_generator.rb
with the Ruby code below. If the _plugins
directory does not exist in your root directory, create it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Let’s decompose the plugin code above:
- The first lines bind this plugin to run whenever the
post_write
hook atposts
scope is triggered.posts
scope is also known ascontainer
in Jekyll docs. What does this mean? This plugin runs whenever a post file changes on disk, i.e. whenever you save a post file. - The script then builds an
all_existing_tags
variable. This is a list of the names of the files under_tags
(lines 2-4). - Lines 6-9 call
generate_tag_file
for each tag in the file just saved.generate_tag_file
is called only when that tag does not exist already inall_existing_tags
. - The remaining lines, lines 12-21, are the
generate_tag_file
function, which: - receives a
tag
as paramter; the tag name - generates new tag markdown file under
_tags/
- generates new feed XML file under
_tags/
Cool! Now you know how to extend this too!
Final test
Add a tag3
for the test post above. Do not create a tag markdown file or a feed XML file for it. Only test these paths work after saving the file:
/tags/tag3/
/feeds/tag3.xml
Notice that you earlier had tag2
as well. Repeat the test above for tag2
as well. Why? The plugin should have created files for tag2
as well.
End result
Once finished, the structure in the project’s root directory looks like this:
_layouts
|- feed.xml
|- page.html
|- post.html
|- tags.html
...
_posts
...
_tags
|- tag1.md
|- tag2.md
|- tag3.md
...
feeds
|- tag1.xml
|- tag2.xml
|- tag3.xml
Credits
I arrived at the above solution/structure after having read the below:
I.e. their ideas led to the proposed solution.
Feedback appreciated!
Comments !