How toBuild your own RSS Reader
RSS is a standard way to use XML to share content from a website so other apps could read it and show it in a different format.
Years ago, Google Reader was the best tool in the market to consume RSS. When Google killed it to promote Google+ (which was also killed) other tools started to compete but most of them are not as good Google Reader was or they are pay ones.
I used Feedly since 2005, until now, I build my own RSS Reader and you can see it clicking Reader in the navigation. Let's see how you can build one yourself and host it for free.
Stack
The stack I used is the same of this blog, Airtable, Next.js and Vercel.
- Airtable as my DB, I need it to have a list of feeds I can update without re-deploying
- Next.js as the framework to build the UI and fetch data
- Vercel as my hosting platform
Airtable, the database
First of all, we need a way to store our list of RSS feed, this could be a JSON in the repository or any database, I used Airtable. A simple table with three columns
id
as an auto incremental numberurl
as a stringname
as a string
Next.js, the framework
We need to fetch our list of feeds, I have a function getRSSFeeds
like this one:
import Airtable from "airtable";
import { RSSFeed } from "types";
const base = new Airtable({
apiKey: process.env.AIRTABLE_API_KEY,
}).base(process.env.AIRTABLE_BASE);
export async function getRSSFeeds(limit = 100): Promise<RSSFeed[]> {
const table = base("feeds") as Airtable.Table<RSSFeed>;
const records = await table.select({ maxRecords: limit }).firstPage();
return records.map((record) => record.fields);
}
The RSSFeed
type has this shape:
type RSSFeed = {
id: number;
url: string;
name: string;
};
In the pages/reader/index.tsx
file I have a super short getStaticProps function.
import { GetStaticProps } from "next";
import { ReaderProps } from "types";
import { ReaderLayout } from "layouts/reader";
import { getRSSFeeds } from "utils/get-rss-feeds";
export const getStaticProps: GetStaticProps<ReaderProps> = async () => {
const feeds = await getRSSFeeds();
return { props: { feeds }, revalidate: 1 };
};
export default ReaderLayout;
The ReaderProps
type has this shape:
type ReaderProps = { feeds: RSSFeed[] };
As we can see it's basically two lines of code. Note how revalidate
is defined, this enables the incremental regeneration behavior of Next.js for this page, with this if a new feed is added to the Airtable database there is no need to deploy, the page will update itself in background the next time someone access it.
You may notice our ReaderLayout
we export. This has the UI, you can do whatever you can, if you like the one I built for myself check it on GitHub.
Now that we have our list of feeds we can create a page for each individual feed, we can create one in pages/reader/[feed].tsx
, here we need to define a getStaticProps
and a getStaticPaths
methods.
import { GetStaticProps, GetStaticPaths } from "next";
import Parser from "rss-parser";
import { FeedPageProps, FeedPageQuery, Feed } from "types";
import { FeedLayout } from "layouts/feed";
import { getRSSFeeds } from "utils/get-rss-feeds";
export const getStaticProps: GetStaticProps<
FeedPageProps,
FeedPageQuery
> = async ({ params }) => {
const id = Number(params.feed);
const parser = new Parser();
const feeds = await getRSSFeeds();
const { url, name } = feeds.find((feed) => feed.id === id);
const res = await fetch(url);
const rss = await res.text();
const feed = ((await parser.parseString(rss)) as unknown) as Feed;
return { props: { feed, id, name, url }, revalidate: 1 };
};
export const getStaticPaths: GetStaticPaths<FeedPageQuery> = async () => {
const feeds = await getRSSFeeds();
const paths = feeds.map((feed) => {
return { params: { feed: feed.id.toString() } };
});
return { paths, fallback: "blocking" };
};
export default FeedLayout;
Here we need to import the rss-parser
lib which will let us read a string with RSS in XML and return an object with the data from the feed. The code for this is in the getStaticProps
function.
In the getStaticPaths
we call getRSSFeeds
and transform the list of feeds from the Airtable DB to the list of paths. We also enable fallback
as blocking which means if a user access a feed we didn't considered initially in our list of paths we will do SSR and then cache the HTML and continue with SSG.
The FeedPageProps
, FeedPageQuery
and Feed
types have this shapes:
type FeedPageProps = {
id: RSSFeed["id"];
url: RSSFeed["url"];
name: RSSFeed["name"];
feed: Feed;
};
type FeedPageQuery = { feed: string };
type Feed = {
title: string;
description: string;
lastBuildDate: string;
items: Array<{
guid: string;
title: string;
link: string;
content: string;
contentSnippet: string;
isoDate: string;
pubDate: string;
}>;
};
And as with the ReaderLayout
, the exported FeedLayout
is the UI of each individal feed. You can see the code for the one in my blog on GitHub too.
Vercel, the hosting platform
Lastly, we can deploy our site to Vercel, go to Vercel project importer import your GitHub repository.
Vercel will give you free hosting with a small limit of server functions, thank to using getStaticProps
for everything our RSS reader will not be counted and we can have any number of feeds.