Last edited: 2020-07-10

Hand rolling RSS feeds is easy

rss icon


An RSS feed is a standardized news feed typically used for news, blogs or podcasts.

This blog has a simple RSS feed located at https://winther.games/rss.xml.
Generating this RSS file and serving it is dead simple!

Generating the RSS file

The minimal RSS file structure I want is this:

<?xml version="1.0" encoding="utf-8"?>

<rss version="2.0">
    <channel>
        <title>TITLE OF THE SITE</title>
        <link>LINK TO THE SITE</link>
        <description>DESCRIPTION OF SITE</description>
        <lastBuildDate>TIMESTAMP UPDATED EVERYTIME I MAKE CHANGES TO THE SITE</lastBuildDate>
        <language>en-us</language>
        <!-- ALL BLOG POSTS NEED TO HAVE THEIR OWN <item> AREA -->
        <item>
            <title>TITLE OF BLOG POST</title>
            <link>LINK TO BLOG POST</link>
            <guid>LINK TO BLOG POST</guid>
            <pubDate>TIMESTAMP OF FIRST PUBLISHED DATE OF BLOG POST</pubDate>
        </item>
    </channel>
</rss>

This is the complete copy of the initial ugly code:

gen_rss() {
    SSE=$(seconds_since_epoch)
    TIME=$(TZ="" date -d "$(date -d @$SSE '+%Y-%m-%d')" +%s)
    BUILDDATE=$(TZ="" date --rfc-822 -d @$TIME)

    # Sort blog posts in rss.xml by post_first_published_time.
    declare -A TIMESTAMPS
    for POST in $(ls source/post_first_published_time/); do
        TIMESTAMPS[$POST]=$(cat source/post_first_published_time/$POST)
    done

    SORTED_POSTS=$(for TIMESTAMP in "${!TIMESTAMPS[@]}"; do
        echo "$TIMESTAMP" ${TIMESTAMPS["$TIMESTAMP"]};
    done | sort -rn -k2 | cut -d' ' -f1)

    CONTENT='<?xml version="1.0" encoding="utf-8"?>

<rss version="2.0">
    <channel>
        <title>winther.games blog</title>
        <link>https://winther.games/</link>
        <description>nothing but butts</description>'
    CONTENT="$CONTENT
        <lastBuildDate>$BUILDDATE</lastBuildDate>
        <language>en-us</language>"

    for SITE in $SORTED_POSTS; do
        PFPTIME=$(cat source/post_first_published_time/${SITE})
        ANON_DATE=$(date -d @$PFPTIME '+%Y-%m-%d')
        ANON_TIME=$(TZ="" date -d "$ANON_DATE" +%s)
        CONTENT="$CONTENT
        <item>
            <title>$(cat source/post_title/${SITE})</title>
            <link>https://winther.games/${SITE}/</link>
            <guid>https://winther.games/${SITE}/</guid>
            <pubDate>$(TZ="" date --rfc-822 -d @${ANON_TIME})</pubDate>
        </item>"
    done
    CONTENT="$CONTENT
    </channel>
</rss>"

    echo $CONTENT > site/rss.xml
}

It is worth noting that I normalize all timestamps to the resolution of a date and the UTC timezone, since you, dear reader, do not need to know when and where I am working :)

The webserver

This blog is served by NGINX on NixOS, so linking to the RSS feed is done by this statement:

      locations."~ /(feed|feeds|rss)$" = {
        extraConfig = ''
          return 301 /rss.xml;
        '';
      };

In the ‘configuration.nix’.
This redirects all ‘/rss’, ‘/feed’ and ‘/feeds’ requests to the ‘rss.xml’ file.

RSS Autodiscovery

A nifty way to make sure that it is easy for people to add my site to their feed is to implement RSS Autodiscovery.

It is implemented by adding this line to the ‘<head></head>’ of all the HTML files:

<link rel="alternate" type="application/rss+xml" title="winther.games blog" href="/rss.xml" />