RSS has had a bit of a resurgence for personal websites and blogs in recent years, especially with the growing adoption of Small Web and IndieWeb ideologies.
Many static site generators - including Hugo, Jekyll, and Eleventy - can easily support the automatic generation of RSS feeds at build time (either directly, or through plugins).
The same is true for Gatsby - the framework currently used to build this static website - and the good news is that setting up one feed, or multiple ones for different categories, only takes a few minutes.
Your Gatsby blog structure
This article talks about RSS feeds for blogs (a typical use-case), but is also relevant for other notes, podcasts, or anything else that is published periodically to your Gatsby site.
In Gatsby, the typical blog set-up involves the blog entries in markdown format, and a template “page”, which is used to render the markdown blog posts.
You’ll also probably have a “blog” page which lists or paginates your posts for visitors to find them, and a createPages
function in your gatsby-node.js
that generates the pages from the template and markdown.
All this sounds way more complicated than it is in practice, and there are lots of guides available to help set this up.
At the very least, this article assumes you have blog posts written in a directory containing markdown for each post similar to the following:
---
date: "2021-03-04T22:17:00Z"
title: "Easily set up discoverable RSS feeds on a Gatsby website"
description: "How to set up multiple discoverable RSS feeds for your static Gatsby website."
tags: [100daystooffload, technology, javascript]
---
The post content starts here...
The metadata (frontmatter) doesn’t need to be exactly as shown, but having useful metadata (e.g. tags) in-place helps make your feeds richer.
Creating your feeds
To create the feeds, we’ll use a Gatsby plugin called gatsby-plugin-feed
, which will do most of the heavy-lifting for us (as long as you have a blog in place structured similarly to the way described above).
First off, add the plugin as a dependency: yarn add gatsby-plugin-feed
. I also recommend installing moment
to help with formatting dates for the feed (as we’ll see later): yarn add moment
.
Next, you’ll need to create some code in gatsby-config.js
. If you have a blog already then you likely already have content in this file (e.g. gatsby-source-filesystem
configuration). Your file probably looks a little like the following:
module.exports = {
siteMetadata: {
title: 'My Cool Website',
siteUrl: 'https://my.cool.website',
},
plugins: [
{
resolve: 'gatsby-source-filesystem',
options: { ... },
},
'gatsby-plugin-react-helmet',
],
};
Along with any other plugins you may have.
To create the feed we’ll make use of a GraphQL query, and a function which will create a feed object. If we define these separately (as below), it will give us more flexibility later.
In the same file (gatsby-config.js
), at the top, first require
the moment
library we installed earlier, define the query we’ll use, and a function to create a feed object:
const moment = require('moment');
// Query for all blog posts ordered by filename (i.e. date) descending
const rssPostQuery = `
{
allMarkdownRemark(
sort: { order: DESC, fields: [fileAbsolutePath] },
filter: { fields: { slug: { regex: "/blog/" } } }
) {
edges {
node {
html
fields { slug }
frontmatter {
title
description
date
tags
}
}
}
}
}
`;
const createRssPost = (edge, site) => {
const { node } = edge;
const { slug } = node.fields;
return Object.assign({}, edge.node.frontmatter, {
description: edge.node.description,
date: moment.utc(`${node.frontmatter.date}`, 'YYYY/MM/DDTHH:mmZ').format(),
url: site.siteMetadata.siteUrl + slug,
guid: site.siteMetadata.siteUrl + slug,
custom_elements: [{ "content:encoded": edge.node.html }],
});;
};
The rssPostQuery
assumes your blog posts are rendered at /blog/filename
in your built site. If not, then just change this value in the regex. Likewise, the createRssPost
function assumes the dates in the frontmatter of your posts are formatted like YYYY/MM/DDTHH:mmZ
- if not, just change this string to match your own format (I use UTC here as we’re dealing with global audiences!).
Essentially, the GraphQL query string returns all markdown files ordered by descending filename (I title my blog posts by date, so this gives a reverse chronological ordering of posts, with the newest first), and gives us the post content, slug (“path”), and selected fields from the posts’ frontmatters.
We use a regex in the query to discern between different types of markdown files. For example, you may have a collection of notes - also written in markdown - which we want to ignore for the purposes of creating an RSS feed for just blog posts.
The createRssPost
function (which we’ll call later), accepts a markdown file (edge
) and information about the website (site
), and returns a fresh object representing this information to be eventually embedded in the feed.
The guid
field is a globally-unique ID for this post on your blog and reader software will use this to, for example, determine if the user has already seen the post and should mark it as “read”. Since all of my posts have a unique path (“slug”), I just use this for the ID.
Finally, we need to add a section to our plugins
array to tell gatsby-plugin-feed
how to build our feed using the query and function we created above. In the same file, make the following changes:
module.exports = {
siteMetadata: { ... }, // omitted for brevity
plugins: [
{
resolve: 'gatsby-source-filesystem',
options: { ... }, // omitted for brevity
},
{ // Add this object to your "plugins" array:
resolve: 'gatsby-plugin-feed',
options: {
feeds: [
{
serialize: ({ query: { site, allMarkdownRemark } }) =>
allMarkdownRemark.edges.map(e => createRssPost(e, site)),
query: rssPostQuery,
output: '/rss.xml;,
title: 'My Cool Blog',
description: 'All of my blog posts'
},
],
},
},
...
],
};
The gatsby-plugin-feed
plugin only runs when the site is actually built. If you have your Gatsby site running locally, just run gatsby build
in a separate Terminal window and then navigate to /rss.xml
on your local development website to view the feed.
Creating multiple feeds
The example configuration in the previous section creates a single feed containing all blog posts.
However, you may have noticed that the feeds
attribute is an array; this means that the plugin can be used to create multiple feeds. I do exactly that on this website: I have different feeds for different audiences (e.g. for technology, life, books, etc.).
Since we’ve already broken our code out into a separate query and function, it is easy to add new feeds by filter
ing on the markdown edges before passing them to map
in the serialize
function.
If you modify the same file again (gatsby-config.js
), you can create a feed for all of your posts that contain a tag named “technology” as follows:
... // omitted for brevity
{
resolve: 'gatsby-plugin-feed',
options: {
feeds: [
{ ... }, // omitted for brevity
{
serialize: ({ query: { site, allMarkdownRemark } }) =>
allMarkdownRemark.edges.filter(e => {
const tags = e.node.frontmatter.tags;
return tags && tags.length > 0 && tags.indexOf('technology') > -1;
}).map(e => createRssPost(e, site)),
query: rssPostQuery,
output: '/technology.xml',
title: 'My Technology Blog',
description: 'Posts in my blog tagged with "technology".'
},
],
},
},
...
This will create a new feed at /technology.xml
containing these tech posts.
Since it’s just plain old JavaScript, you can use any of the available information to craft a number of flexible feeds for your visitors to subscribe to. You can then list these feeds on a page on your site, like this one.
Feed discovery
The gatsby-plugin-feed
plugin has one more trick up its sleeve: without any extra work it will automatically inject the relevant <link />
tags to your site’s HTML at build-time to list the feeds that you have configured.
This means that your visitors just need to add your site’s root URL (e.g. “https://my.cool.website”) into their feed reader and it will suggest the available feeds to them.
The image above shows the Reeder macOS app automatically listing the available feeds on my website after entering just the root URL for the site. Visitors can then just add the ones they want.