Some human reflections
The current internet is full of ads, scams, and proselytism. This phenomenon even has a name: “enshittification”, a term coined by Cory Doctorow.
At the beginning of the internet, there were passionate people willing to share knowledge and collaborate on common projects. The web felt more human, more normal.
Today, attention has become a resource to be extracted. Platforms are optimized for engagement, not understanding. Content is buried under advertising, dark patterns, and algorithmic noise.
In this context, I feel that RSS is underestimated — and that it could be a niche, or even a refuge, from this bloated, advertising-driven internet.
Let’s get back to RSS feeds
RSS feels outdated to many, yet it remains one of the most elegant tools the web has ever produced. It is simple, decentralized, and user-controlled.
I believe RSS could be reclaimed by communities. Here are a few arguments:
- Selective consumption: you choose the topics that genuinely interest you.
- Homemade blogs first: not big corporations trying to monetize your attention.
- No endless scrolling: you read what’s new, then you stop.
- Peer-to-peer knowledge: direct connections between writers and readers.
- Simple user experience: no FOMO, no forced continuity, no social-proof mechanics.
RSS doesn’t try to hook you. It doesn’t rank, recommend, or manipulate. It simply delivers content you explicitly asked for.
So let’s take care of our feeds — and maybe, by doing so, take care of the web a little too.
Config customization
All the RSS config are done in .config/hugo.yaml
- Limit the number of articles in your RSS feed to not overwhelm the feed.
1services:
2 RSS:
3 limit: 10
- Change the default
index.xmltofeed.xmland produce only one feed
1# Change the default index.xml to feed.xml
2outputFormats:
3 RSS:
4 mediatype: "application/rss"
5 baseName: "feed"
6
7outputs:
8 home:
9 - HTML
10 - Offline # required by PWA module for displaying the offline pages.
11 - RSS
12 - SearchIndex # required by search module.
13 - WebAppManifest # required by PWA module to make your site installable.
14 # default outputs for other kinds of pages (avoid produce RSS unecessarily).
15 section: ['html']
16 taxonomy: ['html']
17 term: ['html']
Customize the output
To customize the XML of your RSS feed, you need to override the default template. For this, create a file named layouts/_default/rss.xml with the default Hugo rss.xml, then adapt it.
For example in my case - display just type “page” (not the Categories, Authors, etc ) and only “posts” section, then I diplay the full content with my Github avatar as front image (since I do not have regular name for each post):
1 {{- range where (where .Site.Pages ".Section" "posts") "Kind" "page" }}
2 <item>
3 <title>{{ .Title }}</title>
4 <link>{{ .Permalink }}</link>
5 <pubDate>{{ .PublishDate.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
6 {{- with $authorEmail }}<author>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</author>{{ end }}
7 <guid>{{ .Permalink }}</guid>
8 {{- $content := safeHTML (.Content | html) -}}
9 <description>
10 {{- with index .Params.images 0 }}
11 {{- $imageURL := printf "%s%s" $.Site.BaseURL . | absURL }}
12 {{ "<" | html }}img src="{{ $imageURL }}"
13 alt="Featured image for {{ $.Title }}"
14 width="600" height="400"
15 {{ "/>" | html }}
16 {{- end }}
17 {{ $content }}
18 </description>
19 </item>
20 {{- end }}
Nice RSS optimization from Romain Blog which also remember some basic facts:
Beyond standard feed readers, a clean RSS feed is the backbone of automated newsletters. Tools like Mailchimp, ConvertKit, or MailerLite have features called “RSS-to-Email”.
Check and Validate
Validate RSS feed with W3C Feed Validation Service.
Check with a RSS reader like FeedDesk for Windows or FreshRSS for Linux users the final output.
Find RSS feed from a blog here
Bonus
🐍 Here is the bonus, a python script to convert a yaml file to OPML file that I can import it in my feedDesk.
1# Example usage:
2# python3 content/posts/howto-customize-feed-rss/codes/yaml-to-opml.py data/rss/blogs.yaml -o blogs.opml --include-inactive
3
4import argparse
5import yaml
6import xml.etree.ElementTree as ET
7from xml.dom import minidom
8from datetime import datetime
9from pathlib import Path
10
11
12def prettify(elem):
13 rough_string = ET.tostring(elem, "utf-8")
14 reparsed = minidom.parseString(rough_string)
15 return reparsed.toprettyxml(indent=" ")
16
17
18def yaml_to_opml(input_path: Path, output_path: Path, include_inactive: bool):
19 with input_path.open("r", encoding="utf-8") as f:
20 feeds = yaml.safe_load(f)
21
22 opml = ET.Element("opml", version="1.0")
23
24 head = ET.SubElement(opml, "head")
25 ET.SubElement(head, "title").text = "RSS Feeds"
26 ET.SubElement(head, "dateCreated").text = datetime.utcnow().isoformat()
27
28 body = ET.SubElement(opml, "body")
29
30 for feed in feeds:
31 if not feed.get("feed"):
32 # Explicit rule: no feed → skip
33 continue
34
35 if not feed.get("active", False) and not include_inactive:
36 continue
37
38 outline = ET.SubElement(
39 body,
40 "outline",
41 text=feed["name"],
42 title=feed["name"],
43 type="rss",
44 xmlUrl=feed["feed"],
45 htmlUrl=feed.get("url", ""),
46 )
47
48 if feed.get("description"):
49 outline.set("description", feed["description"])
50
51 if not feed.get("active", False):
52 outline.set("category", "inactive")
53
54 output_path.write_text(prettify(opml), encoding="utf-8")
55
56
57def main():
58 parser = argparse.ArgumentParser(
59 description="Convert a YAML RSS list to OPML"
60 )
61 parser.add_argument(
62 "input",
63 type=Path,
64 help="Path to input YAML file",
65 )
66 parser.add_argument(
67 "-o",
68 "--output",
69 type=Path,
70 help="Path to output OPML file (default: same name as input)",
71 )
72 parser.add_argument(
73 "--include-inactive",
74 action="store_true",
75 help="Include feeds marked as inactive",
76 )
77
78 args = parser.parse_args()
79
80 if not args.input.exists():
81 parser.error(f"Input file does not exist: {args.input}")
82
83 output_path = args.output or args.input.with_suffix(".opml")
84
85 yaml_to_opml(
86 input_path=args.input,
87 output_path=output_path,
88 include_inactive=args.include_inactive,
89 )
90
91 print(f"✅ OPML written to {output_path}")
92
93
94if __name__ == "__main__":
95 main()
It takes a yaml file with this structure:
1# Name; URL; description; feed; active (true/false)
2- name: Bałtyk Blog
3 url: https://mozebaltyk.github.io/
4 feed: https://mozebaltyk.github.io/feed.xml
5 description: My personal blog about sysadmin and devops topics.
6 active: false
7- name: While True Do
8 url: https://blog.while-true-do.io/
9 feed: https://blog.while-true-do.io/rss/
10 description: A blog about programming, technology, and software development.
11 active: true










