Using Eleventy filters in Directory Computed Data

I’m working on a ton of updates to this site right now. Some of these are pretty big (notice the theme picker up top?), and others are smaller, quality-of-life improvements. I launched the Eleventy version of this site in 2019, when the project was still in its relative infancy, and a lot has changed since then. One of these is the addition of Computed Data, which dropped over a year after I launched.

A big part of my job involves overseeing content strategy, information architecture, and user experience, all of which are impacted to some degree by URL formats. Since I spend a lot of time thinking about this stuff at work, I tend to spend a lot of time thinking about it, period. I’ve never loved the way my permalinks are handled, so this improvement effort was my chance to bring some of the best practices I spend all day yammering about to my own site.

To be clear, I think my permalink format is fine. But the slugify filter doesn’t always do what I want, and while it creates URL-friendly slugs, it doesn’t exactly create human-friendly ones. If I wanted my permalinks to be their best on an individual basis, I had to manually overwrite the format in my directory posts.json using a permalink: key in a post’s yaml frontmatter. That sounds simple enough, but it’s a more error-prone process than it might seem.

Here’s what I was working with in my posts.json:

{
  "layout": "post.njk",
  "tags": ["post"],
  "permalink": "{{ page.date | dateToFormat('yyyy/MM') }}/{{ title | slugify }}/index.html"
}

Nothing crazy, but sticking to this format when manually overriding presented some small problems. For starters, my permalinks include the current date. If I wanted to manually specify a permalink, I need to get that right. I also needed to remember whether I should start them with a slash or not (I think Eleventy normalizes this, but I still want to get it right). And then I need to finish the whole thing with /index.html. This is one of those areas it pays to make the right thing to do the easy thing to do. If I want to make consistently nice URLs, I need to take the work and potential for error out of doing it.

Since consistency is part of the goal here, I knew I wanted to reuse my Eleventy filters, rather than just write some new functions to handle it. The problem is that I couldn’t really find any resources for how to write it. Like, what’s the syntax?

So here it is, a new posts.11tydata.js, which replaces the old posts.json file, and copiously commented:

module.exports = {
  layout: "post",
  tags: ["post"],
  eleventyComputed: {
    // data here is the post's data
    permalink: function (data) {
      if (data.permalink) {
        // First, if I have already specified a permalink, just use it
        return data.permalink;
      }
      else {
        // If there is no permalink, look for a slug in the data. 
        // If there is no slug, just use the slugify filter on the title
        const slug = data.slug ?? this.slugify(data.title);
        
        // Now, create the date portion of the URL
        const dateString = this.dateToFormat(data.date, "yyyy/MM");
        
        // Combine it all for your new, consistent permalink
        return `/${dateString}/${slug}/index.html`;
      }
    }
  }
};

A little more explanation of what’s going on here might be helpful.

First, the ?? operator. This is “nullish coalescing”, which is relatively new, so I feel like it’s worth calling out. It has been supported since Node 14.5.0, but if this fails, check to make sure you’re not on an older version of Node. You could work around it by checking for data.slug in an if statement.

Next, the whole point of this post — the filter. Just use this.filtername(data). That’s all there is to it. I’m using two examples of it above, to create the slug and the dateString variables, which shows an example of a filter with and without a parameter.

Like I said, Eleventy Computed Data makes it easy to use filters on directory data files, but I struggled to find the answer. It’s not that nobody else it doing exactly this — I’m sure many people are — but my googling took me to a lot of answers that weren’t quite what I was looking for. I did stumble across one example, but without context, it wasn’t clear that this was the same thing that I was trying to do. As is often the case, the solution, while actually simple, wasn’t obvious. I hope this can help a few more people take advantage of this powerful feature.

I’ve actually taken the approach a level further and put this function into a common helpers.js file, which I pull into the Directory Data javascript files for four different content directories. This way, all of these can share a common permalink function and get the benefit of the same filters that I use elsewhere throughout my site.

Finally, I want to give a shoutout to Zach Leatherman, the creator of this amazing tool, for being a great maintainer and community builder. When I couldn’t figure it out, I posted on Mastodon, and Zach pointed me in the right direction. Try getting that level of service from Next.js.