dmvrtx

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 &#x2192;
            </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!