RSS Amplifier

BurgeonLab: Full-text · Jun 4, 2025

Things I Learnt About RSS Feeds

0
Sign in to vote or save

Naty S · BurgeonLab

RSS feeds are still relevant, so I will share how to validate them and optimize their functionality in WordPress and Hugo. Learn to add featured images, exclude pages, and fix common bugs like invalid XML. With tips on improving RSS descriptions and templates, this guide ensures your blog's feed is reader-friendly and visually appealing.

This post was last updated 1 year ago. The core ideas should still be useful, but tech moves fast; always check the latest docs for current best practices before applying what is mentioned.

RSS stands for Really Simple Syndication. It is a way of subscribing to content on the Internet. This can be a news website, your favourite blog, a podcast, or even use it to receive newsletters with tools like Kill the Newsletter.

To subscribe, you will need a RSS reader. There are many to choose from, and it is not in the scope of this post, but I personally use Feeder on Android for years now, Fluent Reader on macOS (it has not been updated for a while though, so I might need to look for an alternative). I also have Miniflux (a self-hosted RSS reader) installed using Portainer on my Raspberry Pi homelab but I haven’t set it up yet…

RSS has been around since 1999. Personally, I’ve been a long-time user, subscribing to sites I want to keep an eye on.

You might say that email subscriptions can also keep you up to date, but I always try to avoid subscribing to email newsletters as much as possible. RSS feeds offer a clutter-free alternative to email newsletters, which tend to pile up, leading me to mass delete it all—a waste of everyone’s time and resources. It also gives me “invisible stress”, despite filtering them into the newsletter folder for later reading. RSS removes the worry of email overload.

So if you haven’t turned on RSS for your blog (or haven’t got it set up correctly), please consider doing so, as there are a group of us who use this age-old method of reading content from the web!

Screenshot of a RSS feed being validated.

Getting to this empty page and just a ticked rss valid badge took longer than I expected, but it was worth it

First, ensure the feed.xml is actually generated correctly by checking your RSS feed url with a validator tool. I used RSS Board’s RSS Validator and the one from W3C to good effect.

My sudden intrigue on the RSS status on my blogs was triggered by one of my new friends/follower who asked me what my RSS link was to burgeonlab.com. I realised even though I have set it up (or so I thought!), I forgot to link to it on my page! (Shout-out to joelchrono!)

But after adding the link to the footer, out of curiosity, I decided to check the feed link with a validator. Lo and behold, the feed was completely broken, i.e. not valid and had a long list of “recommendations” which they say can “improve interoperability with the widest range of feed readers by implementing the recommendations.” To me it was errors/bugs that required fixing as it looked pretty bad.

Note

The main takeaway is to give your feed a check once in a while, especially if you have made changes to the theme files, as sometimes relPermalinks, formatting, or other layout changes can affect how the feed is generated.

The two content management systems (CMS) I currently use are WordPress and Hugo.

WordPress

WP does it for you automatically; just add /feed to the end of your site domain to see the RSS feed. If you don’t see it, go to Settings > Permalinks and just scroll to the bottom and click Save Changes without making any changes. This will flush the permalinks. Then go to your caching plugin and Purge All and Clean All for Database Optimization.

While researching and fixing all the bugs in the RSS generation with my current Hugo Anubis2 theme, I realised WP doesn’t actually generate its RSS feed with images! That’s not good enough! 😅 So here’s a snippet to add to your functions.php to add the featured image of each post into your RSS.

Tip

One of my favourite WP plugins is Code Snippets by Shea Bunge. I add all my code snippets and mods with it instead of editing theme code directly.1

 1function featured_image_in_rss($content) {
 2    global $post;
 3    if ( is_feed() ) {
 4        if ( has_post_thumbnail( $post->ID ) ) {
 5            $thumbnail_html = get_the_post_thumbnail( $post->ID, 'medium', array( 'style' => 'margin-bottom: 10px;' ) );
 6
 7            // Check if  'medium' size exists. If not, try 'large' or 'full'.
 8            if ( empty( $thumbnail_html ) ) {
 9                $thumbnail_html = get_the_post_thumbnail( $post->ID, 'large', array( 'style' => 'margin-bottom: 10px;' ) );
10            }
11            if ( empty( $thumbnail_html ) ) {
12                $thumbnail_html = get_the_post_thumbnail( $post->ID, 'full', array( 'style' => 'margin-bottom: 10px;' ) );
13            }
14
15            if ( !empty( $thumbnail_html ) ) {
16                $content = '<div style="margin-bottom: 15px;">' . $thumbnail_html . '</div>' . $content;
17            }
18        }
19    }
20    return $content;
21}
22
23add_filter('the_excerpt_rss', 'featured_image_in_rss', 100); // High priority
24add_filter('the_content_feed', 'featured_image_in_rss', 100);
25
26function filter_image_attributes( $attr, $attachment, $size ) {
27    if ( is_feed() ) {
28        unset( $attr['decoding'] );
29        unset( $attr['sizes'] );
30        unset( $attr['fetchpriority'] );
31    }
32    return $attr;
33}
34add_filter( 'wp_get_attachment_image_attributes', 'filter_image_attributes', 10, 3 );

Refresh your feed in your reader (or clear cache or re-add the feed to see the update)! I’m really glad I delved into all this RSS stuff because it looks way better now in the RSS reader!

Hugo

Now, for the Hugo side of things! I realised, in hindsight, after troubleshooting that many of the issues I had was probably caused by the theme I chose. Because for my other Hugo blog which uses Blowfish theme, there seems to be less “recommendations” generated by the validator; and most importantly, at least is was a valid RSS feed to begin with!

Screenshot of all the errors while validating a RSS feed.

Example of the errors I was seeing when I first started

I will go through a checklist on how I got a “clean” and valid RSS feed with Hugo (in relation to the Anubis2 theme I’m currently using).

Setup Hugo Config

  • Enable RSS feed generation by adding "rss" to the sections you want a feed to be created. I prefer to just stick with one feed: home.
1    [outputs]
2    home = ["html", "rss"]
3    section = ["html"]
4    taxonomy = ["html"]
5    term = ["html"]

Tip

The home RSS feed, by default, includes all recent posts and is set up to pull all posts instead of what is visually set to display on your front page (e.g. when your home page doesn’t actually include any recent posts, it will still work). This is because of this line: .IsHome is true, you set $pctx = .Site which means it will generate the feed by considering all the site pages, independent of the home page content.

  • The home feed page is found on hugosite.com/index.xml. Remember to add a link to it on your site! I have mine originally next to my social icons in the header, but eventually decided to add it to the footer.

Note

Section is the top-level folders inside your /content directory. Taxonomy are the built-in groups like tags and categories (or your own group names). Term is the individual item or tag inside a taxonomy.

RSS Feed TypeDefault URL Pattern
Home (site-wide)/index.xml
Section/section/index.xml
Taxonomy/categories/index.xml
Term/categories/term/index.xml
  • To turn off all RSS, add disableKinds = ["rss"]

  • To reduce the items in the feed, add [services.rss] limit = 20.

  • While we’re in hugo.toml, ensure these settings are filled in correctly to avoid errors later.

 1baseURL = "https://www.domain.com/" # Ensure there is a trailing slash at the end of your baseURL for permalinks to generate correctly.
 2[params]
 3    copyright = "(c) 2025 domain.com"
 4    dateFormat = "2006-01-02"
 5[params.author]
 6    name =  "Name"
 7    email = "name@domain.com"
 8[mediaTypes]
 9    [mediaTypes."application/rss+xml"]
10        delimiter = "."
11        suffixes = ["xml", "rss"]
12[outputFormats]
13    [outputFormats.RSS]
14        mediaType = "application/rss+xml"
15        baseName = "index"
16[services.rss]
17    limit = 20 # Default if unlimited.

Exclude Pages

  • For problem pages (like Pagefind search page) or pages you want to exclude in your RSS feed, add the following to the top of your RSS template, layouts/_default/rss.xml:
1{{- $pages := where (where $pctx.RegularPages ".Params.disable_feed" "!=" true) "Params.hidden" "!=" true -}}
  • Then add disable_feed = true in the front matter (I use +++, i.e. .toml) of the page you want to exclude.
  • Try to ensure there are no <scripts>, <links>, <style>, etc in RSS descriptions to improve readability and compatibility with different readers. Using a summary with summary = 'Insert post summary here.' in the front matter of post instead of the full content can help, especially if you have table of contents/anchors in your post content.

  • plainify strips away all HTML tags like role, aria-hidden, style, to output only plain text. This includes stripping away formatting like <p> or <strong>. If you have a lot of formatted text for the description you can try using safeHTML instead of plainify.

  • htmlEscape is for fixing special characters like symbols into valid XML syntax. For example, &amp; is an ampersand (&) or &ldquo; is a quote ("). This fixes the parsing error: undefined entity.

1<description>{{ .Summary | plainify | htmlEscape }}</description>
  • Adding a cover or featured image to your RSS posts hugely improves the appeal in RSS readers in my opinion! But it is important to know where you place the images. I will try to explain the three locations Hugo supports.

Screenshot of RSS reader Feeder showing BurgeonLab's feed before and after adding images to RSS feed.

Before and after adding images to RSS feed
  • To use an image inside your post’s page bundle, i.e. the directory that holds your particular post’s resources like index.md, img1.webp, add cover = '/img_1.webp' to the front matter.

  • Images inside the same folder as the post/content file are accessible using .Resources.Get and can have Hugo imaging processing like .Fit, .Fill., .Resize, .Crop. For example: {{ $img := .Resources.Get "cover.webp" }}.

  • To use an image outside the page bundle, add it into the /static/images/ folder, then add to the front matter: cover = 'images/generic_post_cover.webp'.

  • Images in static/ are not processed by Hugo, i.e. no image processing like crop can be done. They are referenced by absolute paths (/images/img1.webp).

Tip

There’s one more place to place images in Hugo, which is assets/. These could be images that are used globally around the site or theme images. Hugo imaging processing can be applied. Use resources.Get to reference these.

The difference between the global function and the page object retrieval is the . before resources and capital R.

  • Add this snippet into rss.xml within <item>. The first part is the conditional check for locating the cover image. If the image is inside the page bundle, use .Resources.GetMatch to fetch the permalink of the image. But if the image is outside the page bundle (i.e. a static image that is not a page resource) and not found, use .Params.cover with absURL.
 1{{ $cover := "" }}
 2{{ with $page.Params.cover }}
 3    {{ $res := $page.Resources.GetMatch . }}
 4    {{ if $res }}
 5        {{ $cover = $res.Permalink }}
 6    {{ else }}
 7        {{ $cover = . | absURL }}
 8    {{ end }}
 9{{ end }}
10
11{{ if $cover }}
12    <media:content url="{{ $cover }}" medium="image" />
13    <media:title>Cover Image</media:title>
14{{ end }}
  • Remember to add the Media RSS namespace declaration at the top of the template: xmlns:media="http://search.yahoo.com/mrss/" to the existing XML declaration: <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">.

Here is my current RSS template that I’ve modded to my preference and is validating correctly. You can reference it if needed. It is working with hugo v0.147.7+extended+withdeploy and Anubis2 v.1.3.6. There is a RSS template provided by Hugo in their GitHub too for reference.

 1{{- $pctx := . -}}
 2{{- if .IsHome -}}
 3  {{ $pctx = .Site }}
 4{{- end -}}
 5
 6{{- $pages := where (where $pctx.RegularPages ".Params.disable_feed" "!=" true) "Params.hidden" "!=" true -}}
 7{{- $limit := .Site.Config.Services.RSS.Limit -}}
 8{{- if ge $limit 1 -}}
 9  {{- $pages = $pages | first $limit -}}
10{{- end -}}
11
12  {{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
13
14<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
15
16  <channel>
17    <title>{{ with .Site.Params.feedTitle }}{{ . }}{{ else }}{{ .Site.Title }}{{ end }}</title>
18    <link>{{ .Site.BaseURL }}</link>
19    <description>Recent blog posts on {{ .Site.Title }}</description>
20    {{ with .Site.Params.copyright }}
21    <copyright>{{ . }}</copyright>{{ end }}
22    <generator>Hugo (https://gohugo.io)</generator>
23    {{ with .Site.Language.Locale }}
24    <language>{{.}}</language>{{end}}
25    {{ with .Site.Params.author.name }}
26    <managingEditor>{{ with $.Site.Params.author.email }}{{ . }}{{ with $.Site.Params.author.name }} ({{ . }}){{ end }}{{ end }}</managingEditor>{{ end }}
27    {{ with .Site.Params.author.name }}
28    <webMaster>{{ with $.Site.Params.author.email }}{{ . }}{{ with $.Site.Params.author.name }} ({{ . }}){{ end }}{{ end }}</webMaster>{{ end }}
29    {{ if not .Date.IsZero }}
30    <lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 +0800" | safeHTML }}</lastBuildDate>{{ end }}
31    {{ with .OutputFormats.Get "RSS" }}
32    {{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}{{ end }}
33    {{ range $index, $page := $pages }}
34
35    <item>
36      <title>{{ .Title }}</title>
37      <link>{{ .Permalink }}</link>
38      <pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 +0800" | safeHTML }}</pubDate>
39      {{ with .Site.Params.author.name }}
40      <author>{{ with $.Site.Params.author.email }}{{ . }}{{ with $.Site.Params.author.name }} ({{ . }}){{ end }}{{ end }}</author>{{ end }}
41
42      {{ $cover := "" }}
43      {{ with $page.Params.cover }}
44        {{ $res := $page.Resources.GetMatch . }}
45          {{ if $res }}
46            {{ $cover = $res.Permalink }}
47          {{ else }}
48            {{ $cover = . | absURL }}
49          {{ end }}
50      {{ end }}
51
52      {{ if $cover }}
53        <media:content url="{{ $cover }}" medium="image" />
54        <media:title>Cover Image</media:title>
55      {{ end }}
56
57      <description>
58      {{ .Summary | plainify | htmlEscape }}
59      </description>
60      <guid isPermaLink="true">{{ .Permalink }}</guid>
61    </item>
62  {{ end }}
63  </channel>
64</rss>

The template generates a feed that looks like this:

Screenshot of generated XML feed of a Hugo blog

Summary

Working on Hugo’s RSS template gave me a deeper understanding of its backend workings. I now have a solid grasp on how to customize the RSS template and fix common errors. To know all the required elements and its syntax, read this RSS specification guide and this for Media RSS specs.

In the end, I actually submitted the RSS template to my Hugo theme’s repo (Anubis2) and the author, Junyi, actually accept my pull request straight ahead to fix all the RSS validation errors! It was a big achievement for newbie GitHub contributor like me… It was way less intimidating once you’ve done it the first time; so definitely give it a shot if you managed to sort some code out in a FOSS project you like! 🥳

I hope this post can help you with your RSS feed generation as well, improving your blog traffic and gain more readers! And if you enjoy Hugo-related content, please consider following my blog for more tips and tutorials. 😉


  1. If you want to a more step-by-step guide on how to use the Code Snippet plugin, check out my post on adding a caption to featured images↩︎

Read the original on burgeonlab.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.