Hugo Atom Syndication XML Template (Better RSS)

Hugo Atom Syndication XML Template (Better RSS)

Jhaura Wachsman
Jhaura Wachsman

The Atom Syndication feed format is an alternative to the more common and well-known RSS 2.0 format. The Atom protocol provides a number of advantages over is older rival, making it a worthwhile addition to your Hugo website. To make the switch in Hugo we need to create an Atom specific XML feed template and link it in our theme's HTML Head section.

Preparation

New Hugo Site

By way of example, let's create a fresh Hugo website installation:

When typing/copying the terminal commands below, don't include the prompt portion: ~ %

# Assuming a working directory named "sites"
~ % cd sites/
sites % hugo new site hugo-atom
sites % cd hugo-atom/

# Add a theme
hugo-atom % git init
hugo-atom % git submodule add https://github.com/budparr/gohugo-theme-ananke.git themes/ananke

# Add theme to config
hugo-atom % echo 'theme = "ananke"' >> config.toml

# Add two "Posts"
hugo-atom % hugo new posts/first-post.md
hugo-atom % hugo new posts/second-post.md

Now that we have a fresh working environment and a couple of posts, we can begin to implement our Atom Feed XML template for our Hugo website.

Assumptions / Opinions

For this example, let's consider the common "Blog" setup, with a single section called "Posts" (or "Blog", or "Articles").

Furthermore, we will go with these opinions:

  • We only want one main feed and it's located in site public root
  • Only "Posts" should show up in the feed, not static pages, such as About, Contact, or Privacy
  • Full article content should be in the feed, not just a summary
  • Our feed should be auto-discoverable and linked in the HTML Head section (<head>) of our theme

See below, for how the feed template can be modified to support multiple Sections, such as "Posts" and "Projects".

Setup Steps

Add Hugo Config Settings

Open the Hugo Config file (hugo-atom/config.toml) in your favorite editor and add these settings, being careful not to delete its current contents:

# RSS limit, remove for unlimited posts
rssLimit = 100

[outputs]
# Output HTML, and ATOM on Home
home = ["HTML", "ATOM"]
# Output only HTML everywhere else
section = ["HTML"]
taxonomy = ["HTML"]
taxonomyTerm = ["HTML"]

# Define a new ATOM output format
[outputFormats]
[outputFormats.ATOM]
name = "ATOM"
baseName = "feed"
mediaType = "application/atom+xml"

# Define a new ATOM media type
[mediaTypes]
[mediaTypes."application/atom+xml"]
suffixes = ["atom"]

# Define the main Sections
[params]
mainSections = ["posts"]

# Example Brand settings
[params.brand]
tagline = "Publisher's Tagline"
icon = "icon.png"
logo = "logo.png"

Authors Data File

Create an Authors file (authors.toml) in the Data folder (hugo-atom/data):

TOML is used here, but you can use YAML, JSON, or any of the supported Data file formats.

hugo-atom % touch data/authors.toml

Open the Authors file (authors.toml) in your editor and copy/paste this data:

[default]
name = "Site Publisher"
uri = "https://www.publisher.com/"
email = "contact@publisher.com"
twitter = "publisher"
image = "default.png"

[joesmith]
name = "Joe Smith"
uri = "https://www.joesmith.com/"
email = "joe@joesmith.com"
twitter = "joesmith"
image = "joesmith.png"

Open the First Post file (hugo-atom/content/posts/first-post.md) in your editor, and add an Author Id in the front matter to attribute the post to Joe Smith, and a wee bit of content:

---
author: joesmith
---

First Post.

Atom Feed Layout Template

Create an Atom Feed template file (index.atom) for your Hugo website in the Layouts folder (hugo-atom/layouts):

hugo-atom % touch layouts/index.atom

Open the new Atom Feed file (index.atom) in your editor, and copy/paste this code:

{{- $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections -}}
{{- $limit := .Site.Config.Services.RSS.Limit -}}
{{- if ge $limit 1 -}}
{{- $pages = $pages | first $limit -}}
{{- end -}}
{{ printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="{{ .Site.LanguageCode }}">
  <title>{{ .Site.Title }}</title>
  {{- with .Site.Params.brand.tagline }}
  <subtitle>{{ . }}</subtitle>
  {{- end }}
  <id>{{ "/" | absLangURL }}</id>
  <author>
    <name>{{ .Site.Title }}</name>
    <uri>{{ "/" | absLangURL }}</uri>
  </author>
  <generator>Hugo gohugo.io</generator>
  {{- with .Site.Copyright }}
  <rights>{{ . }}</rights>
  {{- end }}
  {{- with .Site.Params.brand.icon }}
  <icon>{{ . | absURL }}</icon>
  {{- end }}
  {{- with .Site.Params.brand.logo }}
  <logo>{{ . | absURL }}</logo>
  {{- end }}
  <updated>{{ dateFormat "2006-01-02T15:04:05Z" now.UTC | safeHTML }}</updated>
  {{- with .OutputFormats.Get "ATOM" }}
  {{ printf `<link rel="self" type="%s" href="%s" hreflang="%s"/>` .MediaType.Type .Permalink $.Site.LanguageCode | safeHTML }}
  {{- end }}
  {{- range .AlternativeOutputFormats }}
  {{ printf `<link rel="alternate" type="%s" href="%s" hreflang="%s"/>` .MediaType.Type .Permalink $.Site.LanguageCode | safeHTML }}
  {{- end }}
  {{- range $pages }}
  <entry>
    <title>{{ .Title }}</title>
    {{- $author := index .Site.Data.authors (.Params.author | default "default") }}
    <author>
      <name>{{ $author.name }}</name>
      <uri>{{ $author.uri }}</uri>
    </author>
    <id>{{ .Permalink }}</id>
    {{- if .IsTranslated -}}
    {{ range .Translations }}
    <link rel="alternate" href="{{ .Permalink }}" hreflang="{{ .Language.Lang }}"/>
    {{- end -}}
    {{ end }}
    <updated>{{ dateFormat "2006-01-02T15:04:05Z" .Lastmod.UTC | safeHTML }}</updated>
    <published>{{ dateFormat "2006-01-02T15:04:05Z" .Date.UTC | safeHTML }}</published>
    <content type="html">{{ trim .Content "\n" }}</content>
  </entry>
  {{- end }}
</feed>

Testing it Out

Build the Hugo Website

Run this simplified Hugo command in your terminal app to build the website static files into the Public directory (hugo-atom/public):

hugo-atom % hugo --buildDrafts --cleanDestinationDir

Examine the Atom Feed XML

If everything went according to plan, you should see the generated Atom Feed file (feed.atom) in the Public directory (hugo-atom/public/feed.atom). Open it up in your editor and take a look at the output to verify that everything looks correct.

Notice how the Second Post entry has the Default author's details, while the First Post entry has Joe Smith's details? Because we specified him by Id in the First Post's front matter, he is shown as the Author.

Validate the Atom Feed XML

When you feel the Atom Feed XML output is production ready, copy/paste the contents of the generated file and validate it via direct input using the W3C Feed Validator Service.

When validating via direct input ignore any "self reference doesn't match document location" or "content should not be blank" errors. Later, validate your feed by URL once deployed live and these errors shouldn't be present.

Atom Feed Auto-Discovery

Add a Link Element in Your Theme

As a final step, we need to let the world know we have an Atom Feed available for our "Posts" Section. We do this with a Link element (<link>) to our Atom Feed in the Head section (<head>) of our theme's BaseOf file (baseof.html).

In an earlier step, we added a new Atom output format (outputFormats.ATOM) in our config file. Some themes may not automatically add the Link element in the Head section, so we may need to set that up. This is true of the default Hugo theme (ananke v2.54), as it uses an RSS specific method of looking for alternative output formats. To fix this, we need to replace that code snippet with a more up-to-date method. To do this:

  • Copy the Ananke theme's BaseOf file (hugo-atom/themes/ananke/layouts/_default/baseof.html)
  • Paste the file in the site root Layouts folder (hugo-atom/layouts/_default/)

Next, open the pasted BaseOf file in your editor and replace the code snippet at line: 30:

{{ if .OutputFormats.Get "RSS" }}
{{ with .OutputFormats.Get "RSS" }}
  <link href="{{ .RelPermalink }}" rel="alternate" type="application/rss+xml" title="{{ $.Site.Title }}" />
  <link href="{{ .RelPermalink }}" rel="feed" type="application/rss+xml" title="{{ $.Site.Title }}" />
  {{ end }}
{{ end }}

Replace the entire block above with this snippet:

{{- range .AlternativeOutputFormats }}
{{ printf `<link rel="%s" type="%s" href="%s" hreflang="%s"/>` .Rel .MediaType.Type .Permalink $.Site.LanguageCode | safeHTML }}
{{- end }}

Run the simplified Hugo build command again:

hugo-atom % hugo --buildDrafts --cleanDestinationDir

Open the website's generated Home page (hugo-atom/public/index.html) in your editor, and look down at line: 25, you should see the Link element (<link>) with an HREF (href) pointing your Atom Feed XML file's live public URL (http://example.org/feed.atom):

<link rel="alternate" type="application/atom+xml" href="http://example.org/feed.atom" hreflang="en-us"/>

Hurray!

If you see the output above, congratulations, you have successfully implemented the Atom Feed Syndication protocol and Auto-discovery on your Hugo website!

Key Elements of the Template Explained

Include Only Pages from the Posts Section

The snippet below is responsible for pulling in RegularPages from the main Sections. This excludes static pages like About, Contact, or Privacy, and list pages like a "Posts" archive index:

{{- $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections -}}

Tip: You can modify this snippet to include entries from multiple Sections such as "Posts" and "Projects", by editing the where clause.

Limiting the Number of Entries

If the config file setting rssLimit is set, we respect that with this snippet:

{{- $limit := .Site.Config.Services.RSS.Limit -}}
...

Full Post Content, Not Just Summary

Users reading your content via feed aggregation services such as Feedly, will thank you for generating your feed with full post content:

<content type="html">{{ trim .Content "\n" }}</content>

Example Project Source Code

GitHub Repository

For reference, I've created a GitHub repository with the source code used in this article, at: https://github.com/jhauraw/hugo-atom-xml-template

Parting Thoughts

Getting the Right Fit

It's likely your site directory structure will differ. Hugo certainly allows for innumerable setups. However, hopefully you can take the core tweaks outlined here and spin up a supercharged Hugo Atom Syndication XML Feed that works for your needs.

The Atom feed format provides a number of advantages over RSS 2.0 and making the switch is well worth the effort. Remember, make sure to validate your feed before going live!