Using Hugo content sections for RSS feed flexibility

I’ve done a lot of testing with Hugo’s RSS feed generation in the past week, and one fact stands out: you can fight the law, but the law always wins.

In Hugo’s case, the law is content sections. Content sections are the key to RSS flexibility.

You may remember me expressing some bitterness about the way Hugo’s default is to litter the place about with RSS feeds for every single type of taxonomy that you have enabled. Having an RSS feed for every tag is silly, and so I figured I would just disable that. Turns out, it’s not so simple.

As I discovered last week, there’s a lack of granularity for enabling and disabling RSS feeds beyond sections, taxonomies, and taxonomy terms. That sounds great, until you realize I’m not talking about specific sections, taxonomies, and taxonomy terms. I mean all sections, or all taxonomies, or all taxonomy terms. Hugo RSS feeds are all or nothing for each of those.

You can make taxonomy specific empty RSS templates which will force Hugo to not create a feed for that taxonomy, but my thinking was that I’d have to do that for every tag folder under /Tags. Now that I think about it, I could probably create an empty/layouts/tags/rss.xml, and individual tags would inherit that.

I’ll have to experiment with it sometime. I didn’t understand Hugo’s template inheritance model before I started seriously messing with section specific RSS feeds.

But content sections are beautiful because in order to put something in a content section, you need do nothing more than drop it into a folder in /content/contentsectionname. If you need maximum RSS feed flexibility, plan your sections accordingly.

Sections are defined by the physical folder layout, not by definition in the content front matter. In addition, you can create unique RSS templates for each section and subsection on your site in /layouts. And finally, you can make sure a given section or subsection does not have an RSS feed by creating an empty RSS.xml file for that section in /layouts.

First, some terms. While any folder inside /content is automatically a section, not every folder inside a section is automatically a subsection. To steal a section (see what I did there?) from the Hugo docs:

content
 blog        <-- Section, because first-level dir under content/
     funny-cats
        mypost.md
        kittens         <-- Section, because contains _index.md
            _index.md
     tech                <-- Section, because contains _index.md
         _index.md

So to make something a section, it needs to be a folder or subfolder in /content, and if it’s a subfolder, it also needs an _index.md file in place.

Per the Hugo docs again:

_index.md has a special role in Hugo. It allows you to add front matter and content to your list templates

Another fun fact about subsections: calling .Section on a subsection page returns the top level section in the subsection’s path hierarchy. So if you want to make lists of subsections, you need to assign them a Type in the front matter in _index.md and in each post .md file in that subsection.

The easiest system is to just give them a Type matching the folder name. If the subsection folder is named “hugo”, set

type = "hugo"

in each page's front matter in the subsection. Then you can query pages by Type:

{{ range ( where .Site.RegularPages "Type" "hugo" ) }}

For root level section master feeds containing parent and subsections (one level of subsections), you can use a union query. For example, to get all posts in /blog and one level of subsections, you would use this query:

{{ $pages := where .Site.RegularPages "Section" "blog" }}
{{ $pages := $pages | union ( where .Site.RegularPages "Section" .Sections ) }}
{{ range $pages }}

Here’s the weird part of that last query: it works in the site root as well (like for a master RSS feed).

On my site, I have several top level sections, but I want my master feed to just contain everything in /blog and its subsections (I only go one level deep with subsections). By using the exact union query shown above, I get what I want for the master RSS feed in the site root. And I’m not really sure how that works, because I wouldn’t expect .Sections to apply to the first variable assignment in the union outside of the section it references, but it does.

I did expect that query to work for /layouts/blog/rss.xml, but I was surprised to find that it also works for /layouts/rss.xml.

I’m ok with a little mystery if it gets me the result I need.

I won’t tell you how long it took me to figure out that I could just use “Section” .Sections in the union query to get all posts in /content/section and all /content/section/subsections, but there may have been some swearing involved.

The other frustration of Hugo is the contorted query syntax. Combining “where” with “and” or “or” can get weird fast. I still don’t really get it. It takes a lot of experimentation and compile error messages for me to figure out what Hugo is looking for.