Between Google Chrome experimenting with “following” sites, along with a growing frustration of how social media platforms limit a creator’s reach to their fans through algorithmic feeds, there’s been renewed interest in RSS feeds and they’re primed for a comeback as we get into 2022.
This research is brought to you by support from Frontend Masters, CSS-Tricks’ official learning partner.
Need front-end development training?
Frontend Masters is the best place to get it. They have courses on all the most important front-end technologies. Interested in going full-stack? Here’s your best bet:
You may have heard whispers that “RSS in dead” around the web, but the truth is that they are still widely used as virtually every podcast uses one. Maybe you used to be an RSS fan and need to get re-acquainted with it. Or maybe you’re like Chris here at CSS-Tricks and still love RSS. Whatever the case, RSS is a web technology like any other, and there are best practices for how to create and curate a feed.
That’s exactly what I’m going to walk you through in this article. Let’s talk about the different kinds of feeds, how to implement them, and what strategies you can use to get the most out of your feed content.
RSS vs. Atom vs. JSON
Believe it or not, RSS is just one format among other types of syndicated web feeds. The most common formats are:
I’ve used RSS to signify these formats since it’s a far more popular search term but I’ll refer to these technologies as web feeds in this article unless I’m referring to a specific format.
While Atom, RSS and, JSON feeds accomplish the same thing, there are a few differences between them:
- Atom and RSS are based on XML while a JSON feed is based on, well, JSON.
- All of these formats can be extended in some way. In a JSON feed, this is done by adding an object with a key that starts with an underscore anywhere in a feed’s object. With Atom and RSS, you do this by declaring the namespace on the root element. One example of this is on podcast feeds which declare the iTunes podcast namespace, allowing for the use of
<itunes:*>
tags. - JSON feed is a newer feed format meaning that support for it might not be as broad as Atom or RSS. If you have a podcast, however, RSS is a must.
- While all of the formats require a unique identifier for each entry/item, Atom takes it a step further as it requires a unique identifier for every feed.
- All allow HTML markup, though they handle it differently. JSON uses the
content_html
key containing JSON escaped HTML. Atom uses thecontent
tag withtype=html
containing XML escaped HTML. RSS uses the<description>
tag (or thecontent
extension) which either contains XML-escaped HTML or the HTML unescaped in a<![CDATA[]]>
tag.
Other than these things, there are only minor differences between them. You might think that the size of the file could be a possible difference, but compression reduces them all to just a few kilobytes apiece. Unless your application has a specific use case that requires a specific format (like podcasts), it doesn’t hurt to provide multiple formats, but RSS and Atom have the most support.
What makes a “good” feed?
Let’s look at some best practices for making feeds. Like most things on the web, there are things we can do to optimize our work to get the most out of it.
1. It’s easy to find
It doesn’t help to have a feed if no one knows about it. And one way to make a feed discoverable is to link it up in the <head>
of your site. This way, feed readers are able to crawl and recognize when a site offers a content feed.
Here’s an example showing all three formats linked up in the document head:
<head> <link rel="alternate" type="application/rss+xml" href="https://codelab.farai.xyz/index.rss.xml" title="Farai's Codelab's RSS Feed" /> <link rel="alternate" type="application/feed+json" href="https://codelab.farai.xyz/index.feed.json" title="Farai's Codelab's JSON Feed" /> <link rel="alternate" type="application/atom+xml" href="https://codelab.farai.xyz/index.atom.xml" title="Farai's Codelab's ATOM Feed" /> <!-- etc. -->
</head>
And, yes, it’s OK to use all three! You can specify as many links as you want, though some feed readers might only recognize the first one. The important thing is that one includes rel="alternate"
and the feed’s MIME type
. There’s also the option to add a title which never hurts.
What else can you do to make your feed easy to find? Advertise it! Place direct links to the feeds somewhere prominent on your site that people can use to copy and paste into their feed reader.
That’s what CSS Tricks does. This is the link to the site’s RSS and it’s available in the footer across the entire site. Some feed readers can pick up on these links, too, even though they are outside of the <head>
.
As for what to name the feed itself, it doesn’t matter as long as it’s discoverable. Here’s a good look into how various sites name their feeds, but I’ve named mine feed.json
, feed.rss.xml
and feed.atom.xml
for JSON feed, Atom, and RSS respectively.
2. It takes advantage of HTTP
There are certain basic features of the web that can be leveraged to make your feeds a little better.
For example, be sure to compress your feeds, as it greatly reduces the overall file size and the time to download it. Most servers can take care of this for you, using gzip, Brotli, or some other compression engine.
Likewise, it’s a good idea to support either ETags or If-Modified-Since as they both allow clients to cache feeds and informs the browser whether a newer version of the feed is ready before it is downloaded. Much like compression, your server may take care of this as well.
Another thing to do: enable permissive CORS. Otherwise, clients could be blocked from fetching the feed. And while you should consider the security implications of letting any old site fetch your feed, it’s highly unlikely that it becomes a major issue for most small sites and feeds. This one-liner is all you need to enable CORS:
Access-Control-Allow-Origin: *
3. It displays full content instead of summaries
This is totally a user experience thing. You may have even experienced this before, where you subscribe to an RSS feed and all you get is the first paragraph or a summary of the post. Why? The traditional thinking is that providing only a summary encourages users to click through to your site, thereby leading to more visits. And more visits equals more eyeballs, which equals more revenue, etc.
I suggest avoiding that and instead allow your feeds to send the entire content for each post/entry/item. Many users prefer reading content in a feed reader because of the emphasis they place on legibility.
If you’re concerned about some dishonest person scraping your content and displaying it on their own site because you’re feeding the full content, let me reassure you: it’s no harder to a web page than a syndicated feed.
And if you’re a publisher who relies on display ads, and are concerned about the impact that sending full content might have on your revenue: you can still add static ads directly into your feed content. Besides, some readers can parse the web page associated with a feed entry so it can be read in the reader as well.
But let’s not be dogmatic and all, because there are situations where summaries make sense. One is when a feed has a bunch of long-form entries. Another is when you have rich content that can only be viewed in a particular way (think show notes for a podcast). In that case, try making a good summary. One example is Nielsen Norman Group’s RSS which has a summary and an excerpt up to the first <h2>
tag.
If I ever decide to only show summaries in my feed, I’d make sure to include an image, an outline of the content’s main points, and a link to the canonical version in addition to the summary. It is a bit of work but it gives the reader an idea what to expect, unlike some feeds I’ve seen which awkwardly truncate content to just the first few words.
4. It is designed for reading
When crafting content, consider how it might be seen outside the context of a web browser, in places where JavaScript and CSS are limited. Sara Soueidan has a bunch of tips and ideas that are worth checking out. The main idea: provide a good fallback experience for your content.
This is a mostly an issue when it comes to embedded elements. While some embeds contain fallback content in their markup (like Twitter’s embedded tweets and CodePen’s embedded pens), others might not. Certain embeds (including videos posted to Vimeo) can only be embedded on certain domains meaning those won’t show up in a feed reader. So you need provide a way to view it somehow, like an image or a link to a webpage.
There are plenty of ways to do fallbacks. Twitter’s embed falls back to a <blockquote>
— which makes total sense as a tweet is sort of like a quote — and a link to the tweet itself, which allows some clients that do not support embeds, like Outlook, to effectively render the content in a way that is still accessible to the user.
Though NetNewsWire is good with embeds, YouTube sometimes prevents it from playing videos like here. So, instead, the embed falls back to a link that points the user to watch it on YouTube’s site. Outlook doesn’t support YouTube embeds (or any embeds at all), but a descriptive link to the video on YouTube is still available.
The moral of the story: know your readers and how they render content so you can provide the best fallback experience possible.
Beware of relative URLs
One big issue across feeds is resolving relative URLs for images and links. Resolving based off the feed’s canonical link might work, but what happens if that link is in a subdirectory? The XML formats could use the xml:base
attribute which defines the base URL to use when resolving relative URLs, but that’s only supported by Atom and is ignored and deprecated by most readers.
The most robust solution is to use absolute URLs for every href
and src
in an entry’s content. Meaning that the markup looks something like this:
<p>Read <a href="https://css-tricks.com/archives/">all our articles</a>.</p>
…and neither this:
<p>Read <a href="/archives/">all our articles</a>.</p>
…nor this:
<p>Read <a href="archives/">all our articles</a>.</p>
This is hard to do automatically, moreso with statically-generated sites. One approach is to make relative URLs absolute after compiling the feed in a build pipeline. Another approach is to manipulate the way Markdown links and images are rendered by your static site generator so that the URLs are absolute. I hope that more static site generators allow the second option but, for now, Hugo is the only static site generator that supports this through Markdown render hooks.
But wait, there’s an exception to this rule. And it’s footnotes. Some readers can detect footnotes and handle them. Here’s some HTML that should work in any feed reader that supports relative jump links:
<p>They’d managed to place 27.9MB of images onto the Critical Path. Almost 30MB of previously non-render blocking assets had just been turned into blocking ones on purpose with no escape hatch. Start render time was as high as 27.1s over a cable connection<sup id="fnref:1"> <a href="#fn:1" class="footnote">1</a></sup>.</p> <div class="footnotes"> <ol> <li id="fn:1"> <p>5Mb up, 1Mb down, 28ms RTT. <a href="#fnref:1" class="reversefootnote">↩</a></p> </li> </ol>
</div>
How to handle ads in feeds
You’re unlikely to get JavaScript support inside of an RSS reader, and that means no ads connected to an ad server. In other words, ads will need to be part of your content rather than something that is dynamically injected into place.
PSA: Not all content needs to be included in a feed
I’ve seen feeds in which every piece of content published is packed in and made available all the way back to the very first entry in the feed. I also see plenty of feeds from publishers who post dozens of entries a day. In both cases, I suggest limiting both the amount of content that’s available from past archives and considering multiple feeds instead of one.
Perfect example. Check out MacRumors.com’s feed because it’s extremely active with dozens of new articles published daily. Can you imagine going back to an article from, say, 10 years ago in that feed? Likely not. Unless the feed is for a podcast where storing every episode makes sense, try limiting the number of entries stored in your feed, as users are likely more interested in newer content. This reduces bandwidth and reduces update times which especially counts since users have many feeds to refresh.
I am tempted to say that 10–15 posts is enough to store and display at a time, but like many things, “it depends.” While storing a few makes sense for a site that pushes new content a few times a month, other sites that post way more frequently might eclipse that in a day. So, really, the ideal number of posts is going to depend on the type of content you publish (is it timely or evergreen?) and both the volume and frequency of what you publish (is it a lot throughout the day or a few times a month?).
But what I’m really trying to get at is that you want to avoid overwhelming users by inundating them with a pile of articles to get through. A couple of ways to avoid that include:
- displaying summaries instead of full content (see, another exception to a previous rule!), and
- filtering content so that users can choose from multiple feeds for specific types of content. In other words, if you want to provide every single post (or a complete timeline on a particular topic), consider making a dedicated feed for that topic/category/tag/whatever.
The reason I’m so fussy about the size of a feed is that—like images, scripts, and other assets—the number and size of feeds affect the performance of a feed reader. The more feeds a user is subscribed to and the more entries that need to be fetched from those feeds add to the time it takes to refresh and display that content.
Moving feeds
Like websites might change domains, you may need to move a feed from its current address. It’s not terribly difficult to do, but there are important things to consider before making a move.
For instance, it’s a good idea to ensure that your feed’s items have a global unique identifier (GUID). This maps out to feed’s guid
in RSS and its id
in both Atom and JSON. The GUI prevents feed readers from fetching duplicate entries. This is all the more important (and challenging) if you’re working with a static site.
While it may be tempting to use the entry’s permalink as an identifier, remember, those can change. To make a GUID, I’d recommend looking into using a tag URI. A tag URI consists of:
- an authority (i.e., the domain of the site)
- a date (that indicates a point in time that the tagging entity controlled the authority name associated with the feed)
- the specific URL to fetch
- a fragment (which might be a sub resource or a timestamp)
tag:<authority>,<YYYY-MM-DD>:<specific>#<fragment>
The <specific>
portion could be something like the relative portion of your site’s homepage URL (i.e. /
) and the fragment can be the content’s published timestamp. For instance, a post here on CSS Tricks could have a tag URI that looks like this:
tag:css-tricks.com,2021-16-11:/#1637082038781
This way, the authority date ensures that even if the domain changed hands. Plus, it can be managed in a static site generator as you can track domain changes over time.
The biggest reason I suggest the tag URI scheme is that Atom requires a feed’s id
to be in a URL format. Even though RSS and JSON don’t have the same constraint, the tag URI scheme works for them as well, meaning we have full support.
And, with a robust id in place, a feed can be safely moved without feed readers pulling in duplicate entries. To move the feed itself, set up a 301 redirect to the new location and you’re done.
You might come across a technique called the XML redirect in which a file containing the feed’s new location is placed at the old location. As great as this would work for times when you can’t manipulate HTTP codes, I couldn’t find any feed readers which implement this.
Validating a feed
Feeds, like HTML, need to be valid in order to properly work. The benefit of a validated feed is that you know your code is free from errors and that entries are properly flowing from your site to feed readers.
W3C’s feed validation service is one option for RSS and Atom feeds. You provide the URL to the feed or paste the feed’s actual code, and you’ll get a full report that shows whether you’re hitting all the best practices. You’re likely to get warnings. It happens. Most warnings are really just a heads up and might not have an impact on the feed.
That said, there are two things that should always be addressed when validating a feed:
item
should contain aguid
element: The unique identifier, as we saw, prevents a feed reader from showing the same entry twice when a feed moves.element
should contain absolute URL references—these are hard for readers to resolve, so avoid relative URLs where possible.
What about JSON? To validate a JSON feed, try either using validator.jsonfeed.org or verifying against the JSON Feed schema using any JSON schema validator.
Managing or restricting access to a feed
You know how you can subscribe to a paid podcast and you get access to a special feed URL that contains all the “premium” content you gain access to with your subscription? Well, that’s because we can control who has access to a particular feed while locking others out from receiving the content.
There are two techniques for managing access to a feed:
- HTTP basic authentication requires a username and password, which are either prompted for the user to provide or inferred in the feed URL itself, e.g.
https://username:password@domain.example/path
. - Providing a token as a query parameter, e.g.
http://domain.com/path?token=xyz
As long as the URL is HTTPS, they have the same security, as the URL paths and passwords are encrypted. As for handling authentication it on the server, that’s a whole other topic though there are quite a few articles on it right here on CSS-Tricks.
Join the RSS Club!
OK, so the first rule of the RSS Club is:
Don’t talk about it. Let people find it. Make it worthwhile.
But I’m going to talk about it because feeds that are part of the RSS Club are excellent examples of tailored feeds. That’s because the feed entries are only available in those feeds. In other words, the blog posts are published, but never display on the actual site — they’re only accessible by feed.
Dave Rupert founded the club a number of years ago and it’s a great way to make RSS a first-class citizen for consuming content within a small community.
Joining the club means having a dedicated feed for posts that are only available in that feed. For example, in WordPress, you could create a new “RSS Club” category and filter it out of the main post query. That way, you’re able to either provide a feed just for that category, or the full feed that still includes posts in that category.
(Sorry for spilling the beans, Dave!)
Web feeds beyond content
RSS can be used for more things than blog posts or articles. For instance, GitHub has Atom feeds for issues, commits, pull requests, and releases.
They can also be used to provide updates. Let’s say you wanted a feed that notifies you when there are changes to your website. That’s a great idea, right? Always nice to know what’s happening, especially when there’s more than one cook in the kitchen.
You could build some sort of system that polls your feed periodically for changes then trigger a new feed entry, but that requires a lot of resources. Another idea is to implement webhooks you tell where to look for changes. Then again, managing and sending out notifications can be a hassle, especially if all you want is to monitor content.
I think it’s worth checking out WebSub. You, as a publisher, tell a hub that the site has changed, and the hub notifies whatever system that’s subscribed to the site’s web feeds. You can publish your feed to an existing hub — like Google’s PubSubHubbub Hub — then specify the hub in your feeds. YouTube has implemented this.
Examples!
What’s a tutorial like this without a few good examples? Let’s look at three real-world examples.
1. RSS Podcast
Did you know that CSS-Tricks has a podcast for an ongoing series that covers web history? Well, it does. And, yes, you can subscribe to it via RSS.
Podcasts must use RSS with the xmlns:content
and xmlns:itunes
extensions, which are needed to provide metadata about the podcast and its episodes. The audio file for each episode is specified in an enclosure along with its mime type and size. RSS is limited to one enclosure, but both Atom and JSON support multiple enclosures.
Here’s the feed. Notice the iTunes-specific tags as well as other bits of information that are provided for additional context:
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"> <channel> <atom:link href="https://adactio.s3.amazonaws.com/audio/narration/web_history/podcast.xml" rel="self" type="application/rss+xml" /> <title>Web History</title> <link>https://css-tricks.com/category/history/</link> <language>en</language> <copyright>2020</copyright> <description>Written by Jay Hoffmann and narrated by Jeremy Keith.</description> <image> <url>https://adactio.s3.amazonaws.com/audio/narration/web_history/WebHistoryPodcast.jpg</url> <title>Web History</title> <link>https://css-tricks.com/category/history/</link> </image> <itunes:author>Jay Hoffman</itunes:author> <itunes:summary>The history of the web.</itunes:summary> <itunes:explicit>no</itunes:explicit> <itunes:type>episodic</itunes:type> <itunes:owner> <itunes:name>Jeremy Keith</itunes:name> <itunes:email>jeremy@adactio.com</itunes:email> </itunes:owner> <itunes:image href="https://adactio.s3.amazonaws.com/audio/narration/web_history/WebHistoryPodcast.jpg"/> <itunes:category text="Technology"></itunes:category> <item> <title>Chapter 10: Browser Wars</title> <description>In June of 1995, representatives from Microsoft arrived at the Netscape offices. The stated goal was to find ways to work together—Netscape as the single dominant force in the browser market and Microsoft as a tech giant just beginning to consider the implications of the Internet. Both groups, however, were suspicious of ulterior motives.</description> <pubDate>Mon, 8 Nov 2021 12:00:00 -0000</pubDate> <link>https://css-tricks.com/chapter-10-browser-wars/</link> <itunes:title>Chapter 10: Browser Wars</itunes:title> <itunes:episode>10</itunes:episode> <itunes:episodeType>full</itunes:episodeType> <itunes:author>Jay Hoffman</itunes:author> <itunes:summary>In June of 1995, representatives from Microsoft arrived at the Netscape offices. The stated goal was to find ways to work together—Netscape as the single dominant force in the browser market and Microsoft as a tech giant just beginning to consider the implications of the Internet. Both groups, however, were suspicious of ulterior motives.</itunes:summary> <content:encoded> <![CDATA[ <p>In June of 1995, representatives from Microsoft arrived at the Netscape offices. The stated goal was to find ways to work together—Netscape as the single dominant force in the browser market and Microsoft as a tech giant just beginning to consider the implications of the Internet. Both groups, however, were suspicious of ulterior motives.</p> ]]> </content:encoded> <itunes:duration>00:40:40</itunes:duration> <guid>https://adactio.s3.amazonaws.com/audio/narration/web_history/Chapter_10_Browser_Wars.mp3</guid> <enclosure url="https://adactio.s3.amazonaws.com/audio/narration/web_history/Chapter_10_Browser_Wars.mp3" length="19608877" type="audio/mpeg"/> </item>
</channel>
</rss>
2. RSS for posts
Let’s look to CSS-Tricks once again, this time for an example of what a pretty standard RSS feed of blog posts looks like.
The code for this particular RSS feed is a little more verbose than your typical feed, and that’s to do with the multiple extensions added to the <rss>
tag. A number of them aren’t reachable but there are some that handle other things, like xmlns:wfw
for comments, xmlns:dc
for additional metadata, and xmlns:sy
for information on how often the feed is refreshed.
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"> <channel> <title>CSS-Tricks</title> <atom:link href="https://css-tricks.com/feed/" rel="self" type="application/rss+xml" /> <link>https://css-tricks.com</link> <description>Tips, Tricks, and Techniques on using Cascading Style Sheets.</description> <lastBuildDate>Fri, 19 Nov 2021 15:13:49 +0000</lastBuildDate> <language>en-US</language> <sy:updatePeriod> hourly </sy:updatePeriod> <sy:updateFrequency> 1 </sy:updateFrequency> <generator>https://wordpress.org/?v=5.8.2</generator> <image> <url>https://i1.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1</url> <title>CSS-Tricks</title> <link>https://css-tricks.com</link> <width>32</width> <height>32</height> </image> <site xmlns="com-wordpress:feed-additions:1">45537868</site> <item> <title>Parallax Powered by CSS Custom Properties</title> <link>https://css-tricks.com/parallax-powered-by-css-custom-properties/</link> <comments>https://css-tricks.com/parallax-powered-by-css-custom-properties/#respond</comments> <dc:creator> <![CDATA[Jhey Tompkins]]> </dc:creator> <pubDate>Fri, 19 Nov 2021 15:13:46 +0000</pubDate> <category> <![CDATA[Article]]> </category> <category> <![CDATA[animation]]> </category> <category> <![CDATA[custom properties]]> </category> <category> <![CDATA[GSAP]]> </category> <guid isPermaLink="false">https://css-tricks.com/?p=357192</guid> <description> <![CDATA[
]]> </content:encoded> <wfw:commentRss>https://css-tricks.com/parallax-powered-by-css-custom-properties/feed/</wfw:commentRss> <slash:comments>0</slash:comments> <post-id xmlns="com-wordpress:feed-additions:1">357192</post-id> </item> </channel>
</rss>
3. JSON feed
This is actually my personal feed and I just so happen to use JSON for it. It’s pretty bare bones and less cluttered than the other examples because, as far as I know, there are no JSON extensions like the RSS ones we saw in Example 2.
I find that JSON is much easier to read and understand all that’s needed is an object with the feed data rather than writing out the entire template.
{ "author": { "name": "Farai Gandiya" }, "feed_url": "https://codelab.farai.xyz/feed.json", "home_page_url": "https://codelab.farai.xyz/", "icon": "https://codelab.farai.xyz/fcl-logo.png", "items": [ { "content_html": "...", "date_modified": "2021-11-13T05:26:07+02:00", "date_published": "2021-11-13T05:26:07+02:00", "id": "https://codelab.farai.xyz/1636773967", "summary": "...", "title": "Don't be afraid of the Big Long Page by Amy Hupe, content designer.", "url": "https://codelab.farai.xyz/links/long-content-ok/" } ]
}
Web Feed Implementations Across CMSs and Static Site Generators
Many CMSs and static site generators support web feeds, though it’s usually RSS as it has the widest support. Here are some CMSs that support web feeds:
- WordPress (Atom and RSS)
- Yoast SEO lets you modify content before and after each feed’s entries.
- WordPress JSON feed plugin
- Ghost (Modifiable RSS)
- Shopify (RSS)
- Squarespace (RSS for posts and podcasts)
- Wix (RSS)
And here’s some resources on adding web feeds (again mostly RSS) to various static site generators
Wrapping up
And there you have it! This is what I believe be nearly everything you need to consider when implementing a web feed. We looked at three different formats (RSS, Atom, JSON), covered best practices for creating a user-friendly feed reading experience, walked through validating a feed, covered the possibility of authenticating feeds, looked at three real-world examples of feeds in the wild, and provided some implementations across various technologies.
(Oh, and there was that thing where the first rule of the thing is not to talk about the thing.)
I hope these guidelines empower you to make resilient web feeds. If you have any questions on implementing a web feed, or you just feel like sharing your RSS feed, please do leave a comment!