<?xml version="1.0" encoding="utf-8"?>
  <rss version="2.0"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
    xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
    xmlns:georss="http://www.georss.org/georss"
    xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
  >
    <channel>
      <title>Piccalilli - Eleventy topic archive</title>
      <link>https://piccalil.li/</link>
      <atom:link href="https://piccalil.li/category/eleventy.xml" rel="self" type="application/rss+xml" />
      <description>We are Piccalilli. A publication dedicated to providing high quality educational content to level up your front-end skills.</description>
      <language>en-GB</language>
      <copyright>Piccalilli - Eleventy topic archive 2026</copyright>
      <docs>https://www.rssboard.org/rss-specification</docs>
      <pubDate>Tue, 07 Apr 2026 01:02:03 GMT</pubDate>
      <lastBuildDate>Tue, 07 Apr 2026 01:02:03 GMT</lastBuildDate>

      
      <item>
        <title>Low-tech Eleventy Categories</title>
        <link>https://piccalil.li/blog/low-tech-eleventy-categories/?ref=eleventy-category-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Fri, 26 Jan 2024 00:00:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/low-tech-eleventy-categories/?ref=eleventy-category-rss-feed</guid>
        <description><![CDATA[<p>One of my favourite features of WordPress is how quick and easy it is to categorise posts, then even better, grab an RSS feed for that category. It’s what powers <a href="https://set.studio/category/newsletter/">Set Studio’s newsletter</a>, for example.</p>
<p>I’ve been doing some recent updates to this site and although the category system has been in place for a number of years, I wanted to add the ability for readers to subscribe to specific categories via RSS too.</p>
<p>In this post, what I’m going to do is first, show you how to implement a very low-tech category system and then, expand that into an RSS feed for each category. I’m also going to focus solely on JavaScript, HTML and Nunjucks so you can apply whatever front-end stuff you want with no dramas.</p>
<div>
  <a href="https://low-tech-11ty-categories.netlify.app/">Check out the demo</a>
  <a href="https://github.com/Andy-set-studio/demos/tree/main/low-tech-11ty-categories">Check out the source code</a>
</div>
<h2>Setup</h2>
<div><h2>FYI</h2>
I’m going to presume you have an empty directory, but if not, I hope this makes sense with your existing Eleventy codebase.
</div>
<p>All we need to do here is install some dependencies:</p>
<pre><code>npm i @11ty/eleventy @11ty/eleventy-plugin-rss
</code></pre>
<h2>Eleventy config</h2>
<p>Next, create an <code>.eleventy.js</code> file and add the following to it:</p>
<pre><code>const rssPlugin = require('@11ty/eleventy-plugin-rss');

module.exports = config =&gt; {
  config.addPlugin(rssPlugin);

  config.addCollection('posts', collection =&gt; {
    return [...collection.getFilteredByGlob('./src/posts/*.md')].reverse();
  });

  // Returns an array of tag names
  config.addCollection('categories', collection =&gt; {
    const gatheredTags = [];

    // Go through every piece of content and grab the tags
    collection.getAll().forEach(item =&gt; {
      if (item.data.tags) {
        if (typeof item.data.tags === 'string') {
          gatheredTags.push(item.data.tags);
        } else {
          item.data.tags.forEach(tag =&gt; gatheredTags.push(tag));
        }
      }
    });

    return [...new Set(gatheredTags)];
  });

  return {
    dir: {
      input: 'src',
      output: 'dist'
    }
  };
};
</code></pre>
<div><h2>FYI</h2>
<p>I tend to use <code>config</code> as the Eleventy config variable but Eleventy tends to use <code>eleventyConfig</code> in their documentation, so you might want to be wary of that if you’re going to implement this on your own site.</p>
</div>
<p>The first thing we’re doing is rigging up <a href="https://www.11ty.dev/docs/plugins/rss/">Eleventy’s official RSS plugin</a>. This does all the heavy lifting for us with dates etc.</p>
<p>Next, we create a <code>posts</code> collection by grabbing all the markdown files in the <code>src/posts</code> directory. Pretty straightforward stuff.</p>
<p>Lastly, this is the magic. Each post item in this demo has front matter like this:</p>
<pre><code>---
title: '10 Tips for Website Redesign in 2024'
date: 2024-01-02
tags: ['Tips And Tricks']
---
</code></pre>
<p>Even though this post is about categories, we’re using Eleventy’s existing, and rather powerful tag capabilities to power them. I like the tags setup because it creates a collection for each <code>tag</code> found in content like the above. Sure, you could roll out a whole custom category operation, but this is the <strong>path of least resistance</strong>.</p>
<p>Let me just remind you of the snippet of JavaScript we’re focusing our attention on. Don’t copy this one into your project because you already have it.</p>
<pre><code>config.addCollection('categories', collection =&gt; {
	const gatheredTags = [];
	
	// Go through every piece of content and grab the tags
	collection.getAll().forEach(item =&gt; {
	  if (item.data.tags) {
	    if (typeof item.data.tags === 'string') {
	      gatheredTags.push(item.data.tags);
	    } else {
	      item.data.tags.forEach(tag =&gt; gatheredTags.push(tag));
	    }
	  }
	});
	
	return [...new Set(gatheredTags)];
});
</code></pre>
<p>The aim of the game with this collection is to return back an array of unique tags that are 100% guaranteed to have content attached to them because we extract them from content that references them.</p>
<p>The first thing we do is create an empty array to store strings in. Then — using Eleventy’s very handy “all” collection — we loop every item in the system and push the tags into this array. If the <code>tags</code> value is a string, it’s pushed straight in, but if it’s an array, a quick loop and push is in order.</p>
<p>Using a <code>Set</code>, all the duplicates are stripped out, then using the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax">array spread operator</a>, we turn the <code>Set</code> back into an array and return it.</p>
<div><h2>FYI</h2> 
<p>You might be thinking, “Andy, why the heck are you not <a href="https://www.11ty.dev/docs/quicktips/tag-pages/">paginating over the collections like 11ty suggests</a>?”</p>
<p>It’s a valid question. The main reason is past project trauma 😅</p>
<p>We’ve had client projects that pushed a bit too much stuff into the nunjucks templates / front matter, so stuff got out of hand <em>real</em> quick. As a symptom of that, I prefer to bring as much stuff as possible into JavaScript.</p>
<p>The logic of gathering the tags isn’t exactly elegant, <em>but</em>, say we wanted scheduled posts, or posts that require a certain level of access, or posts that are live <em>but</em> de-indexed: having the logic in the Eleventy config — or even a separate utility — gives us that long term maintainability.</p>
<p>Areas of sites like category feeds always seem to get missed too — especially with static site generators — so this approach stops that from happening.</p>
</div>
<p>That’s all that needs to be done with the Eleventy config.</p>
<h2>Templates</h2>
<p>Everything that the user can see is using the following layout as a base, which is created on the following path: <code>src/_includes/base.njk</code>. Here’s the code:</p>
<pre><code>&lt;!doctype html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;{{ title }}&lt;/title&gt;
  &lt;/head&gt;
  &lt;main&gt;
    {% if page.url != '/' %}
      &lt;header&gt;
        &lt;a href="/"&gt;Back home&lt;/a&gt;
      &lt;/header&gt;
    {% endif %}
    {% block content %}{% endblock %}
  &lt;/main&gt;
&lt;/html&gt;
</code></pre>
<p>There’s nothing much to cover here. I just added a link back to home if we’re not on the homepage to make the demo easier to navigate.</p>
<p>The only other layout is the post layout, which extends the <code>base.njk</code> one. It’s created on the following path: <code>src/_includes/post.njk</code>. Here’s the code:</p>
<pre><code>{% extends "base.njk" %}

{% block content %}
  &lt;article&gt;
    &lt;h1&gt;{{ title }}&lt;/h1&gt;
    &lt;time&gt;{{ date }}&lt;/time&gt;
    &lt;h2&gt;Categories&lt;/h2&gt;
    &lt;ul&gt;
      {% for tag in tags %}
        &lt;li&gt;
          &lt;a href="/category/{{ tag | slugify }}"&gt;{{ tag }}&lt;/a&gt;
        &lt;/li&gt;
      {% endfor %}
    &lt;/ul&gt;
    {{ content | safe }}
  &lt;/article&gt;
{% endblock %}
</code></pre>
<p>Again, not much to cover here except the little list of tags. Let’s focus on that.</p>
<p>A tag is stored as a single string, or array of strings. Weirdly, the single tags appear to be loop-able in this context too (don’t ask me how; I haven’t got a clue 😅). All we’re doing is looping this data then <a href="https://www.11ty.dev/docs/filters/slugify/">using the built in <code>slugify</code></a> filter to make a nice URL slug.</p>
<div><h2>FYI</h2> 
<p>I’m going to skip the homepage in this article. If you want that code, go ahead and <a href="https://github.com/Andy-set-studio/demos/blob/main/low-tech-11ty-categories/src/home.njk">grab it from the source code</a>.</p>
</div>
<p>Ok, that’s the basics in place. Let’s generate these category listings and the RSS feeds. Create the following file: <code>src/categories.njk</code>.</p>
<pre><code>---
title: 'Category Archive'
pagination:
  data: collections.categories
  size: 1
  alias: category
permalink: '/category/{{ category | slugify }}/index.html'
---

{% extends "base.njk" %}

{% set posts = collections[category] %}

{% block content %}
  &lt;article&gt;
    &lt;h1&gt;{{ category }}&lt;/h1&gt;
    &lt;p&gt;
      &lt;a href="/category/{{ category | slugify }}.xml"&gt;Subscribe with RSS&lt;/a&gt;
    &lt;/p&gt;
    &lt;ol reversed&gt;
      {% for item in posts %}
        &lt;li&gt;
          &lt;a href="{{ item.url }}"&gt;{{ item.data.title }}&lt;/a&gt;
        &lt;/li&gt;
      {% endfor %}
    &lt;/ol&gt;
  &lt;/article&gt;
{% endblock %}
</code></pre>
<p>Using the <a href="https://www.11ty.dev/docs/pagination/">Eleventy pagination system</a>, we’re looping over each individual category that was generated by the <code>categories</code> collection. Because it is a collection of strings that represent each category, we can:</p>
<ol>
<li>Use that same pattern from earlier to generate a slug, which in turn is used to generate a permalink</li>
<li>Use that category string to filter down the Eleventy collections, returning posts that fall into that category</li>
</ol>
<p>With the <code>posts</code> variable that we <code>{% set %}</code>, it’s a case of looping over them and generating a list of links. Job done!</p>
<p>Ok, last file now. Create <code>src/category-rss.njk</code>. This will be the template for each category’s RSS feed.</p>
<pre><code>---
title: 'Category Archive'
pagination:
  data: collections.categories
  size: 1
  alias: category
permalink: '/category/{{ category | slugify }}.xml'
siteUrl: 'https://example.com'
authorName: 'Example author'
authorEmail: 'hi@example.com'
---
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;feed xmlns="http://www.w3.org/2005/Atom"&gt;
  &lt;title&gt;Low-tech Eleventy Categories - {{ category }}&lt;/title&gt;
  &lt;subtitle&gt;Content filed under “{{ category }}”&lt;/subtitle&gt;
  &lt;link href="{{ siteUrl }}{{ permalink }}" rel="self"/&gt;
  &lt;link href="{{ siteUrl }}/"/&gt;
  &lt;updated&gt;{{ collections.blog | rssLastUpdatedDate }}&lt;/updated&gt;
  &lt;id&gt;{{ siteUrl }}&lt;/id&gt;
  &lt;author&gt;
    &lt;name&gt;{{ authorName }}&lt;/name&gt;
    &lt;email&gt;{{ authorEmail }}&lt;/email&gt;
  &lt;/author&gt;
  {% for post in collections[category].reverse() %}
    {% set absolutePostUrl %}{{ siteUrl }}{{ post.url | url }}{% endset %}
    &lt;entry&gt;
      &lt;title&gt;{{ post.data.title }}&lt;/title&gt;
      &lt;link href="{{ absolutePostUrl }}"/&gt;
      &lt;updated&gt;{{ post.date | rssDate }}&lt;/updated&gt;
      &lt;id&gt;{{ absolutePostUrl }}&lt;/id&gt;
      &lt;content type="html"&gt;
        &lt;![CDATA[{{ post.templateContent | safe }}]]&gt;
      &lt;/content&gt;
    &lt;/entry&gt;
  {% endfor %}
&lt;/feed&gt;
</code></pre>
<p>The pagination mechanism is exactly the same as the previous template. The only difference this time is we’re looping the posts in each category to generate an <code>&lt;entry&gt;</code> for the RSS feed.</p>
<p>This is also where the <a href="https://www.11ty.dev/docs/plugins/rss/">Eleventy RSS plugin</a> shines because it both works out when our feed was last updated with <code>rssLastUpdatedDate</code> and converts each post’s date into an RSS-friendly date with <code>rssDate</code>.</p>
<p>I personally like to render the entire post content with <code>post.templateContent</code> in the RSS feed. I think people should be able to read a post in their reader of choice (even with ads on this site). It’s also how I like to read posts in <a href="https://feedbin.com/">Feedbin</a>.</p>
<p>The last thing to cover is I added some extra front matter: <code>siteUrl</code>, <code>authorName</code> and <code>authorEmail</code>. I tend to set them in a <a href="https://css-tricks.com/give-your-eleventy-site-superpowers-with-environment-variables/">more global configuration</a>, but again, I’m keeping this post simple.</p>
<h2>Wrapping up</h2>
<p>I hope you can see how quickly you can get powerful functionality together if you choose the path of least resistance and using the tools available to you, with a light touch of forward thinking.</p>
<p>I’d love to see others using this sort of setup on their static sites too! We’re currently in the process of moving this site to <a href="https://astro.build/">Astro</a> in <a href="https://set.studio/">the studio</a>, so I imagine I’ll do a follow up of this setup on that platform in the future.</p>
<div>
  <a href="https://low-tech-11ty-categories.netlify.app/">Check out the demo</a>
  <a href="https://github.com/Andy-set-studio/demos/tree/main/low-tech-11ty-categories">Check out the source code</a>
</div>
        
        ]]></description>
        
      </item>
    
      <item>
        <title>Create a JSON feed with 11ty</title>
        <link>https://piccalil.li/blog/create-json-feed-eleventy/?ref=eleventy-category-rss-feed</link>
        <dc:creator><![CDATA[Andy Bell]]></dc:creator>
        <pubDate>Wed, 23 Sep 2020 00:00:00 GMT</pubDate>
        <guid isPermaLink="true">https://piccalil.li/blog/create-json-feed-eleventy/?ref=eleventy-category-rss-feed</guid>
        <description><![CDATA[<p><a href="https://piccalil.li//11ty.dev">Eleventy</a> is <em>great</em> because you can literally output <em>anything</em>. To output a JSON feed with 11ty, all you need to do is create a file called <code>feed.njk</code> and adding the following to it:</p>
<pre><code>---
permalink: '/my-cool-feed.json'
---
{
  "posts": [
    {% for item in collections.posts %}
      {
        "title": "{{ item.data.title }}",
        "url": "{{ item.url }}"
      }{% if not loop.last %},{% endif %}
    {% endfor %}
  ]
}
</code></pre>
<div><h2>FYI</h2>
<p>This example is using Nunjucks, which you can <a href="https://mozilla.github.io/nunjucks/templating.html">find documentation for here</a>.</p>
<p>We use the <a href="https://mozilla.github.io/nunjucks/templating.html#for"><code>loop.last</code></a> variable to only add a comma at the end of items that aren’t the last item.</p>
</div>
<p>The JSON feed will give you an output like this:</p>
<pre><code>{
  "posts": [
    {
      "title": "Quick and simple image placeholder",
      "url": "/posts/quick-and-simple-image-placeholder/"
    },
    {
      "title": "Color Palette Generator",
      "url": "/posts/color-palette-generator/"
    }
  ]
}
</code></pre>
<p>This little snippet creates a feed of <code>posts</code> that have a <code>title</code> and <code>url</code>. Whatever data you have access to in your <a href="https://www.11ty.dev/docs/collections/">collection</a>, you can output in this feed, too.</p>
<p><strong>Pro tip</strong>: Make sure you set the <code>permalink</code> to have a <code>.json</code> extension so the browser knows that it is <code>application/json</code>. That’ll save you a lot of server hassle!</p>
<p>You can <a href="https://github.com/piccalil-li/eleventy-json-feed">grab a basic Eleventy starter for this demo here</a>.</p>
        
        ]]></description>
        
      </item>
    
    </channel>
  </rss>
