Hugo & Atom feed
It is 2023, yet Hugo is packaged with a feed template that uses poorly standardised RSS format. However, thanks to its extensibility it is easy to make it produce a well-supported Atom feed.
Let’s start with configuration update to support Atom feed output. In the hugo.yaml
we should declare support for the appropriate MIME-type:
mediaTypes:
application/atom+xml:
suffixes:
- xml
outputFormats:
Atom:
mediaType: application/atom+xml
baseName: index
isPlainText: false
Next, set the homepage output formats to support Atom:
outputs:
home:
- HTML
- Atom
Last step is to create a template for Atom feed. Store the following snippet as index.atom.xml
somewhere in the layouts
directory, e.g. under layouts/_default
.
{{- $pctx := . -}}
{{- if .IsHome -}}{{ $pctx = .Site }}{{- end -}}
{{- $pages := slice -}}
{{- if or $.IsHome $.IsSection -}}
{{- $pages = $pctx.RegularPages -}}
{{- else -}}
{{- $pages = $pctx.Pages -}}
{{- end -}}
{{- $limit := .Site.Config.Services.RSS.Limit -}}
{{- if ge $limit 1 -}}
{{- $pages = $pages | first $limit -}}
{{- end -}}
{{- $pages = (where $pages ".Params.unlisted" "!=" "true") -}}
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<feed version="2.0" xmlns="http://www.w3.org/2005/Atom">
<title>{{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }}: {{ .Site.Params.Description }}</title>
<link rel="alternate" type="text/html" language="{{.Site.Language }}" href="{{ .Permalink }}"/>
<link rel="self" type="{{ .MediaType }}" href="/index.xml" />
<subtitle>Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}</subtitle>
<author>{{ with .Site.Author.name }}
<name>{{.}}</name>{{end}}{{ with .Site.Author.email }}
<email>{{.}}</email>{{end}}
<uri>{{ .Permalink }}</uri>
</author>
<id>{{ .Permalink }}</id>
<generator>Hugo -- gohugo.io</generator>{{ with .Site.Copyright }}
<rights>{{.}}</rights>{{end}}{{ if not .Date.IsZero }}
<updated>{{ .Date.Format "2006-01-02T15:04:05Z07:00" | safeHTML }}</updated>{{ end }}
{{ with .OutputFormats.Get "RSS" }}{{ end }}{{ range $pages }}<entry>
<title>{{ .Title }}</title>
<link rel="alternate" type="text/html" href="{{ .Permalink }}"/>
<published>{{ .Date.Format "2006-01-02T15:04:05Z07:00" | safeHTML }}</published>
<updated>{{ .Lastmod.Format "2006-01-02T15:04:05Z07:00" | safeHTML }}</updated>
<summary>{{ .Summary | html }}</summary>
<id>{{ .Permalink }}</id>{{ with .Site.Author.name }}
<author>
<name>{{.}}</name>
</author>{{end}}
</entry>
{{ end }}
</feed>
After these changes Atom feed should be available at /index.xml
path on your website, similar to how it does it here.
If you clicked on the link above, you can see that feed display is different from how it shows with the aforementioned template. Since Atom feed is an XML document it is possible to customise how web browser will display it by providing XSL instructions. And this is exactly what happens here.
First, we need instructions on how to style our Atom feed for the browser. I use a simple conversion, provided below. It can be stored as rss-style.xsl
somewhere in the static
directory.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:atom="http://www.w3.org/2005/Atom">
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<title>
RSS Feed |
<xsl:value-of select="/atom:feed/atom:title"/>
</title>
<meta charset="utf-8"/>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="stylesheet" as="style" href="/css/rss-style.css"/>
</head>
<body>
<header type="info">
<h1>
<!-- https://commons.wikimedia.org/wiki/File:Feed-icon.svg -->
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
class="mr-5"
style="flex-shrink: 0; width: 1em; height: 1em;"
viewBox="0 0 256 256">
<defs>
<linearGradient x1="0.085" y1="0.085" x2="0.915" y2="0.915"
id="RSSg">
<stop offset="0.0" stop-color="#E3702D"/>
<stop offset="0.1071" stop-color="#EA7D31"/>
<stop offset="0.3503" stop-color="#F69537"/>
<stop offset="0.5" stop-color="#FB9E3A"/>
<stop offset="0.7016" stop-color="#EA7C31"/>
<stop offset="0.8866" stop-color="#DE642B"/>
<stop offset="1.0" stop-color="#D95B29"/>
</linearGradient>
</defs>
<rect width="256" height="256" rx="55" ry="55" x="0" y="0"
fill="#CC5D15"/>
<rect width="246" height="246" rx="50" ry="50" x="5" y="5"
fill="#F49C52"/>
<rect width="236" height="236" rx="47" ry="47" x="10" y="10"
fill="url(#RSSg)"/>
<circle cx="68" cy="189" r="24" fill="#FFF"/>
<path
d="M160 213h-34a82 82 0 0 0 -82 -82v-34a116 116 0 0 1 116 116z"
fill="#FFF"/>
<path
d="M184 213A140 140 0 0 0 44 73 V 38a175 175 0 0 1 175 175z"
fill="#FFF"/>
</svg>
<xsl:value-of select="/atom:feed/atom:title"/>
</h1>
<p>
<strong>This is an RSS feed</strong>. Subscribe by copying
the URL from the address bar into your newsreader. Visit
<a href="https://aboutfeeds.com">About Feeds</a> to learn more and get started. It’s free.
</p>
<p>
<a>
<xsl:attribute name="href">
<xsl:value-of select="/atom:feed/atom:link[1]/@href"/>
</xsl:attribute>
Visit Website →
</a>
</p>
</header>
<main>
<h2>Recent blog posts</h2>
<xsl:for-each select="/atom:feed/atom:entry">
<article>
<h3>
<a>
<xsl:attribute name="href">
<xsl:value-of select="atom:link/@href"/>
</xsl:attribute>
<xsl:value-of select="atom:title"/>
</a>
</h3>
<p>
<xsl:value-of select="atom:summary" />
</p>
<footer>
Published on
<xsl:value-of select="substring(atom:published, 0, 11)" />
by
<xsl:value-of select="atom:author/atom:name" />
</footer>
</article>
</xsl:for-each>
</main>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
In the head
block of this style there is a reference to the CSS stylesheet. Let’s create static/css/rss-style.css
and provide a basic formatting:
html {
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
}
body {
padding: 2em;
}
header h1 {
display: flex;
align-items: flex-start;
line-height: 1;
margin-bottom: 2rem;
}
header h1 svg {
margin-right: 1rem;
}
main article {
padding: 1rem;
margin-bottom: 1rem;
}
main article footer {
font-style: italic;
}
And now it is time to tell the browser to use our XSL transformation. It is done by providing a reference to it in the Atom feed template. Add this line before the feed
tag in the index.atom.xml
:
{{- printf "<?xml-stylesheet href=\"/rss-style.xsl\" type=\"text/xsl\"?>" | safeHTML }}
That’s it!