Publishing A Blog Series With Jekyll

27 March 2024 #jekyll #liquid

Related Posts

CSS Paragraph Separators (10 Nov 2023)

Sometimes a blog post can get too long, and it makes sense to split it into multiple parts. For example, I recently posted about my HN Reader project, and split it into multiple posts with each one detailing different aspects of the project (frontend, API, deployment, etc).

Of course, a reader might find a link to a later entry in the series and want to start from the beginning, so it would be nice to link all of the other posts in the series at the top of the page. I could do all of this manually, but as the series gets longer and longer I would have to remember to go back and update every other post in the series to add the new link. I felt that long-term this would be a pain to maintain and could lead to broken or missing links.

Instead, this should be pretty easy to automate with Jekyll, which is the static site generator I use to publish my blog.

Frontmatter YAML

Each blog post on my website starts as a markdown file with a frontmatter header at the beginning, which is just YAML. We can add our own attribute to the post’s frontmatter to keep track of what series it belongs to. We’ll simply call the variable series and set it to whatever string we want. Then, we can set the same series value on other posts to link them together, just like the one linked at the top of this page1.

---
title: "Publishing A Blog Series With Jekyll"
series: jekyll
---

... rest of the post

Liquid Templates

Next we’ll add some template logic to the layout page for our blog posts. Jekyll uses the Liquid template language, which allows us to use conditional logic and loops right inside our markdown files.

If the page has the series attribute, we’ll insert the div that contains the links to other posts in the series. After that, we just iterate through each post on the site, and add a link to any post that has a matching series value. We also skip the post if the title matches the current page’s title, since including a link to the current page would be pointless.

{% if page.series %}
  {% for post in site.posts %}
    {% if post.series == page.series %}
      {% unless post.title == page.title %}
      <a href="{{ post.url }}">{{ post.title }}</a>
      {% endunless %}
    {% endif %}
  {% endfor %}
{% endif %}

HTML Details

This works great so far, but for a long series this could add a lot of content to the top of the page before the actual post starts. Instead, I decided to hide all of the related posts within an HTML <details> section which is collapsed by default.

<details class="series">
  <summary>Related Posts</summary>
  <!-- list of posts goes here -->
  <a href="#">Another post in this series</a>
  <a href="#">Another post in this series</a>
</details>

I added some CSS styles to the section to change the font size, weight, and color. I also decided to change what the expand/collapse icon looked like since I thought the default triangle was boring.

details > summary {
    list-style-type: '[+] ';
}

details[open] > summary {
    list-style-type: '[\00AC] ';
}

That \00AC code point represents the not sign. But other than because I think boolean algebra notation looks cool, why use this? I tried a regular - as well as an en- and em-dash, but these symbols weren’t the same length as the + symbol. As a result, the “Related Posts” text would shift left/right every time you clicked on it and that really annoyed me.

There’s probably a better way to do this to ensure the symbols are the same width across different fonts too, but I couldn’t figure it out and gave up after a couple minutes. The summary::marker pseudo-selector doesn’t support the width property (or many properties in general).

  1. These two posts aren’t really in any kind of series together, but I thought it would be silly to make an entire post about creating my “related posts section” and not actually show the dang thing in action