Home Writing Reading

til / Blog post series in Eleventy

Posts can get long if you want to explain every part of a subject in one post. I wanted to be able to create short posts and connect them in a series. This would make it more manageable for the readers, but also easy to follow along to the next part.

I use Eleventy, a static site generator, to build this website. I couldn’t find any guides on how to create a post series, so I created my own way. For starters, I had to create a custom collection to gather all posts that are in different series.

// .eleventy.js

// Collect all posts that are part of a series
module.exports = function (config) {
  config.addCollection('postSeries', (collection) => {
    let seriesCollection = {}

    for (const post of collection.getAll()) {
      const { series, createdDateTime: date, title, url } = post.data

      if (series) {
        const seriesPost = { title, date, url }

        if (seriesCollection[series]) {
          seriesCollection[series].push(seriesPost)
        } else {
          seriesCollection[series] = [seriesPost]
        }
      }
    }

    return seriesCollection
  })
}

I collect some information for each post that contains series: <name> in the frontmatter (the data at the top of a markdown file). This information will be used to sort and render the posts. I collect the posts in an object with series name as the key and an array of posts as the value. Something like this:

{
  rescript: [
    {
      title: 'ReScript: Using useContext in rescript-react',
      date: '2021-01-28 09:02',
      url: '/posts/using-usecontext-in-rescript-react/',
    },
    // more posts here
  ]
}

Next, I create a filter to find if a post belongs to a series:

// .eleventy.js
// ...

// Find posts in a specific series
config.addFilter('findSeries', (posts, postSeries, postTitle) => {
  if (postSeries) {
    return posts[postSeries]
      .sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())
      .map((post) => ({
        ...post,
        currentPage: post.title === postTitle,
      }))
  }
})

I sort the posts by date and add a boolean value, to indicate if the post is the current page.

To render the series, I combine the custom collection and the filter. I use nunjucks as the templating language.

{% raw %}{% set seriesPosts = collections.postSeries | findSeries(series, title) %}
{% if seriesPosts and series %}
  // Render the posts
{% endif %}{% endraw %}

I create an inline variable called seriesPosts that takes the collection, postSeries, and runs the findSeries filter on it. The pipe, |, means that we pass the data to a filter. If the post is part of a series, we render the list.

Finally, I wanted to keep the series title independent of the series name. To handle this, I created another filter where I can keep the titles.

// .eleventy.js
// ...

// Series titles
config.addFilter('seriesName', (series) => {
  const titles = {
    rescript: 'ReScript',
  }

  return titles[series]
})
<h2>{{ series | seriesName }} series</h2>

  • Loading next post...
  • Loading previous post...