Working With Web Feeds: It’s More Than RSS

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:

  1. RSS
  2. Atom
  3. JSON Feed

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.

On Sept 26–Oct 2, 2021, RSS had 37 points, web 6, atom 2 and jsonfeed 0. S
Google Trends for Atom, JSON, RSS and web feeds. (Source: Google Web Trends)

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 the content tag with type=html containing XML escaped HTML. RSS uses the <description> tag (or the content 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>.

Screenshot of the CSS-Tricks footer showing a column of links with the heading Follow, and links for social networks, including the site's RSS feed. The background is near black, the headings are orange, and the links are light gray.
Oh, you want to subscribe to the CSS-Tricks RSS feed? Please do! Your RSS feed is just as much a thing to follow as any social network.

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.

Dark UI with blue links and light gray text showing an article from a blog.
N/N Group article rendered in the NetNewsWire reader

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.

Showing a post title in white with the published date and time below it in light gray. Below that is an image of a tired looking man with dark slicked back hair holding a 10 of diamonds card. Below that is the first sentence of a post that breaks mid-sentence.
Yes? You want to finish that sentence?

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.

Showing the same article rendered in both Microsoft Outlook (left) and NetNewsWire (right). The article includes three embeds, one from Twitter, one from YouTube, and the third from TinkerCad), where each includes some form of fallback content for a better reading experience.

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.

Showing an advertisement for anima that displays a brightly colored image, text below the image that says our sponsor, followed by the post title in blue, a blurb from the article content, them a blue learn more link.
The Codrops newsletter is an example that does this well. The newsletter includes a sponsored image and text, and clearly indicates the content is sponsored.

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 a guid 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.

WebSub Flow Diagram, Julien Genestoux
Copyright © 2018 World Wide Web Consortium, (MIT, ERCIM, Keio, Beihang).

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:

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!

Web Features That May Not Work As You’d Expect

As the web gets more and more capable, developers are able to make richer online experiences. There are times, however, where some new web capabilities may not work as you would expect in the interest of usability, security and privacy.

I have run into situations like this. Like lazy loading in HTML. It’s easy to drop that attribute onto an image element only to realize… it actually needs more than that to do its thing. We’ll get into that specific one in a moment as we look at a few other features that might not work exactly as you‘d expect.

This limitation has been around for a while, but it does show how browser features can be exploited. One possible exploit is an anchor gets some :visited link style in CSS and is positioned off screen. With the off-screen anchor, one could use JavaScript to change the anchor’s href value and see if a particular href causes the link to appear visited—reconstructing a user’s history in the process.

Known as the CSS History Leak, this was so pervasive at one time that the Federal Trade Commission, the United States’ consumer protection agency, had imposed harsh fines for exploiting it.

These days, attempting to use getComputedStyle on a :visited link returns the style of the unvisited (:link) link instead. That’s just one of those things you have to know because that’s different from how it intuitively ought to work.

There are two approaches ways you could try to get around this, but neither of them are possible.

  1. make the visited link’s style trigger a side effect (e.g. a layout shift), or
  2. leverage the sibling (~ or +) or child (>) CSS selectors to render another style.

Regarding side effects, while there are some clever yet fragile ways to do this, the options we have for styling :visited links are limited and some styles (like background-color) will only work if they’re applied to unvisited links. As for using a sibling or child, executing getComputedStyle on these returns the style as if the link wasn’t visited to begin with.

Browsers don’t cache assets across sites anymore

One advantage of a CDN was that they allowed for a particular resource (like Google Fonts) to be cached in the browser for use across different websites. While this does provide a big performance win, it has grave privacy implications.

Given that an asset that’s already cached will take longer to load than one that’s not, a site could perform a timing attack to not only see your site history but also expose both who you are and your online activity. Jeff Kaufman gives an example:

Unfortunately, a shared cache enables a privacy leak. Summary of the simplest version:

  • I want to know if you’re a moderator on www.forum.example.
  • I know that only pages under load www.forum.example/moderators/header.css.
  • When you visit my page I load www.forum.example/moderators/header.css and see if it came from cache.

In light of this, browsers don’t offer this anymore.

performance.now() may be inaccurate

A scary group of vulnerabilities came out as couple of years ago, one of which was called Spectre. For an in depth explanation, see Google’s leaky.page (works best in Chromium) as a proof of concept. But for the purposes of this article, just know that the exploit relies on getting highly accurate timing, which is something that performance.now() provides, to try and map sensitive CPU data.

Text about the demo on the left side of the page and two black Germaine-looking code blocks on the right side with a black background and green text.
The demo at leaky.page

To mitigate Spectre, browsers have reduced its accuracy and may add noise as well. These range from 20μs to 1ms and can be changed based on various conditions like HTTP headers and browser settings.

Lazy loading with the loading attribute doesn’t work without JavaScript

Lazy loading is a technique where assets are only loaded in the browser when it scrolls into the viewport. Until recently, we could only implement this in JavaScript using IntersectionObserver or onscroll. Except for Safari, we can apply the loading attribute to images and iframes (in Chromium) and the browser will handle lazy loading.

Note that lazy loading can’t be polyfilled since an image is probably loading by the time you check for the loading attribute’s support.

Being able to do this in HTML makes it sound like the attribute doesn’t require JavaScript at all, but it does. From the WHATWG spec:

  1. If scripting is disabled for an element, return false.

    Note
    This is an anti-tracking measure, because if a user agent supported lazy loading when scripting is disabled, it would still be possible for a site to track a user’s approximate scroll position throughout a session, by strategically placing images in a page’s markup such that a server can track how many images are requested and when.

I’ve seen articles mention that this attribute is how you support lazy loading “without JavaScript” which isn’t true, though it is true you don’t have to write any.

Browsers can limit features based on user preferences

Some users might opt to heavily restrict browser functionality in the interest of further security and privacy. Firefox and Tor are two browsers that do this through the resist fingerprint setting which does things like reducing the precision of certain variables (dimensions and time), omitting certain variables entirely, limiting or disabling some Web APIs and never matching media queries. WebKit has a document outlining how browsers can approach fingerprint resistance.

Note that this goes beyond the standard anti-tracking features that browsers implement. It’s unlikely that a user will enable this as they would need a very specific threat model to do so. Part of this can be countered with progressive enhancement, graceful degradation, and understanding your users. This limitation is a big issue when you actually need fingerprinting, like fraud detection. So, if it’s absolutely necessary, look for an alternative means.

Screen readers might not relay the semantics of certain elements

Semantic HTML is great for many reasons, most notably that it conveys meaning in markup that software, like screen readers, interpret and announce to users who rely on them to navigate the web. It’s essential for crafting accessible websites. But, at times, those semantics aren’t conveyed—at least how you might expect. Something might be accessible, but still have usability issues.

An example is the way removing a list’s markers removes its semantic meaning in WebKit with VoiceOver enabled. It’s a very common pattern, most notably for site navigation. Apple Accessibility Standards Manager James Craig explains why it’s a usability issue, though, citing the W3C’s Design Principle of Priority of Constituents:

In case of conflict, consider users over authors over implementors over specifiers over theoretical purity. In other words costs or difficulties to the user should be given more weight than costs to authors;

Another case where semantics might not be relayed is with emphasis. Take inline elements like strong, em, mark, ins, del, and data—all elements that have semantic meanings, but are unlikely to be read out because they can get noisy. This can be changed in a user’s screenreader’s settings, but if you really want it to be read you can declare it in visually hidden in the content property of either a :before or :after pseudo-element.

To illustrate this I made a brief example to see how NVDA with Firefox 89 and VoiceOver with Safari 14.6 read out semantic elements.

Unlike VoiceOver, NVDA reads out some of the semantic elements (del, ins and mark) and tries to emphasize text by gradually increasing the volume of emphasized text. Both of them have no trouble reading out the :before/:after psudo-elements however. Also, VoiceOver read out the tag’s brackets (greater than, less than), though both screenreaders have the ability to change how much punctuation is read.

To see whether or not you need to emphasize the emphasis, make sure you test with your users and see what they need. I didn’t focus on the visual aspect but the default styling of emphasis elements may be inconsistent across browsers, so make sure you provide suitable styling to go along with it.

Web storage might not be persistent

The WHATWG Web storage specification includes a section on privacy that outlines possible ways to prevent storage from being a tracking vector. One such way is to make the data expire. This is why Safari controversially limits script writable storage for seven days. Note that this doesn’t apply to “installed” websites added to the home screen.

Conclusion

Interesting, isn’t it? Some web features that we might expect to work a certain way just don’t. That isn’t to say that the features are wrong and need to be fixed, but more of a heads up as we write code.

It’s worth examining your own assumptions during development. Critically examine what your users need and factor it in as you make your site. You’re certainly welcome to work around these these as you encounter them, but in cases where you’re unable to, make sure to find and provide reasonable progressive enhancement and graceful degradation. It’s OK if users don’t experience a website the exact same way in every browser as long as they’re able to do what they need to.

That’s my list of things that don’t work the way I expect them to. What’s on your list? I’m sure you’ve got some and I’d love to see them in the comments!


The post Web Features That May Not Work As You’d Expect appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.