Converting my Hugo instance to Atom syndication

The default syndication mechanism for Hugo is RSS 2.0. This is great, and it works perfectly well, but for a few reasons1 I wanted to provide an Atom feed instead. As I dug into what it would take to make this happen, I uncovered a number of good resources that pointed me in right general direction, but no single comprehensive “how-to” guide that I could follow. I’ve tried to collect up the steps and resources that I used below so that it might help others that want to do the same.

I have also made sanitized versions of the configs and templates that I ended up with available in a GitHub repo for easy consumption. Everything there is MIT License, so consume at will if it helps you.

The steps to do this with Hugo are, roughly:

  • Set up a template that Hugo will use to render your Atom feed
  • Configure Hugo to render an Atom feed
  • Update your site configuration/template(s) to link to the Atom feed rather than the RSS feed

The Atom Template

Your first step on the path to providing Atom instead of (or alongside) RSS is to set up your feed template. This is the template that Hugo will use to render your feed into an XML-formatted structure. The template file index.atom.xml will need to go into the ./layouts subdirectory of your Hugo source tree.

The template generally follows in the same structure as the RSS feed template, though there are some different and additional elements involved. One key difference to remember between Atom and RSS is that Atom uses RFC 3339 date strings (i.e. “2006-01-02T15:04:05-07:00”) rather than RFC 822 date strings (i.e., “Sat, 13 Dec 2003 18:30:02 GMT”).

If you look at the template that I’ve set up, you’ll see a few non-standard variables referenced. These are defined in the sample config files present in the example’s GitHub repo. Some of the pertinent ones:

  • In ./config/_default/author.toml
    • name, used for the global author name of the feed
  • In ./config/_default/params.toml
    • dateFormatAtomFeed = "2006-01-02T15:04:05-07:00", used for the dates in the feed elements
    • dateFormatTag = "2006", used in the feed item id tag
    • feedUUID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", a version 4 UUID to uniquely identify the feed
    • icon = "https://images.c20d.us/cards/feature-square.jpg", the feed’s icon
    • icon96 = "https://images.c20d.us/site-img/profile-96x96.jpg", the feed 96x96 icon, for Feedly
    • logo = "https://images.c20d.us/site-img/round-logo.png", the feed’s logo
    • mainSections = ["posts"], used to constrain feed items to posts (excludes “About”, etc.)

Note that I’m using a version 4 UUID as my feed ID. This could be your site’s URL, but using a UUID decouples your site URL from the feed id, allowing more portability. More info about that here.

Also note that I do not use the default approach of using a post’s URL as the entry id. Rather than the post URL, I use tag URIs for reasons described here.

The last thing to note about my template is that I decided to use the <![CDATA[xxx]]> approach to my entry content. I did this to overcome some strange formatting issues I saw in the content in Feedly and Inoreader, and also because several other high-profile feeds do the same so it seemed like a good approach. Copy what works!

Render the Atom Feed

Now that you have an Atom template, you have to tell Hugo what to do with it. This is actually pretty simple, as all you need to do is add some config.

In your ./config/_default/config.toml, add the following:

[mediaTypes]
  [mediaTypes."application/atom+xml"]
    suffixes = ["xml"]

[outputFormats]
  [outputFormats.ATOM]
    mediaType = "application/atom+xml"
    baseName  = "feed"

The first portion of that added config defines the media type for an Atom feed, and declares that files rendered for that media type will have the suffix .xml.

The second portion defines the output format for the “application/atom+xml” media type, and declares that files rendered for that media type will have a basename of feed.

Put these two together, and Hugo will be able to render your index.atom.xml template to feed.xml.

This only gets you part of the way though. The last step is to tell Hugo to actually render the Atom media type for certain sections of your site. You do this by adding “ATOM” to the sections you want to have Atom rendered for. My config looks like this:

[outputs]
  home = ["ATOM", "HTML", "JSON"]
  page = ["HTML"]
  section = ["HTML"]
  taxonomy = ["HTML"]
  term = ["HTML"]

With this configuration, Hugo will render Atom for the “home” section, but not for pages, sections, taxonomies, or terms. By default those non-“home” sections do render RSS, and I didn’t want that. Setting those sections to just “HTML”, and leaving “RSS” out of the “home” list completely turns off RSS feed generation, which is what I wanted.

Pointing to Atom

At this stage you should have a Hugo site that is generating an Atom feed file (“feed.xml” in your public root), but your site is still pointing to the default RSS feed (“index.xml” in your public root), which you may have actually turned off. You gotta fix that.

You’ll most likely have a link to your feed in two places: in the <head> section of your generated pages; and in your site’s pages with the cool “dot plus two curved lines” glyph to represent your feed source.

In the theme for my blog (Hugo Boostrap), the feed link in <head> is generated by a partial stored at ./layouts/partials/head/feed.html. I simply overrode the theme’s partial with a new one:

{{ with .OutputFormats.Get "atom" }}
    {{ printf `<link rel="%s" type="%s" href="%s" title="%s"/>` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }}
{{ end }}

To fix your RSS glyph link, you’ll need to find where it’s defined your your theme. In Bootstrap, the partial is ./layouts/partials/helpers/social-links.html. Other themes will almost certainly be different. In Bootstrap I made the link code as such:

{{ with .OutputFormats.Get "atom" }}
  <a class="nav-link social-link" target="_blank" href="{{ .Permalink }}" title="Feed" rel="noopener noreferrer">
    <i class="fas fa-fw{{ with $size }} {{ . }}{{ end }} fa-rss"></i>
  </a>
{{ end }}

Wrapping Up

Depending on what theme you’re using, your experience may be pretty easy, or it may be more complex. Either way, you’ll need to iterate through the changes and test things along the way. Below are some links to handy reference information and utilities that were a huge help as I was figuring all of this out.

Hopefully this overview is helpful to someone else. It took me far more digging around and trial/error than I expected it would to Atom-enable this blog, but it was a great way to learn a lot about some of the inner workings of Hugo. Enjoy!


  1. I had a handful of specific reasons that I wanted to move away from RSS in favor of Atom, the most important of which was Atom’s explicit support for post Descriptions as well as Content. I publish a full-content feed, and in RSS you can’t do that and have a description/summary. ↩︎

Comments


Copyright

CC BY-NC-ND 4.0