Published on

Next.js RSS Reader

Authors

RSS is making a comeback. More and more developers add a RSS feed to their blogs and tools like Mailbrew combine RSS with other sources like Twitter or Youtube for a great experience.

In this short tutorial we will implement a simple RSS reader with Next.js.

Create our Next.js app

As we will be styling our app with Tailwind CSS the fastest way to get startet is with the Tailwind CSS example. Run:

npx create-next-app --example with-tailwindcss nextjs-rss-reader
# or
yarn create next-app --example with-tailwindcss nextjs-rss-reader

On our homepage we want to list some interesting RSS feeds. To keep it simple we add them to our code. Let's start by creating a new file lib/rss.js and add our first two RSS feeds.

lib/rss.js
export const FEEDS = [
  {
    slug: "nextjs-blog",
    title: "Next.js Blog",
    url: "https://nextjs.org/feed.xml",
  },
  {
    slug: "vercel-blog",
    title: "Vercel Blog",
    url: "https://vercel.com/atom",
  }
];

Every feed contains a slug, which we will use later for the detail page, a title and the RSS feed url.

Change our default index page to the following:

pages/index.js
import { FEEDS } from "lib/rss";
import Link from "next/link";

export default function Home() {
  return (
    <div className="px-6 py-12 max-w-xl mx-auto">
      <h1 className="font-bold text-5xl mb-12">Interesting RSS Feeds</h1>
      <div className="grid grid-cols-2 gap-4">
        {FEEDS.map((feed) => (
          <Link key={feed.slug} href={`/feeds/${feed.slug}`}>
            <a className="p-4 border border-gray-200 hover:border-gray-500 rounded-lg">
              {feed.title}
            </a>
          </Link>
        ))}
      </div>
    </div>
  );
}

We display our feeds in a two column grid with links to the feed's detail page which we will be building next.

RSS Reader Homepage

Feed detail page

For every feed we want to display all its articles. We get the articles by loading and parsing the RSS feed. We use this great library rss-parser for turning RSS XML feeds into JavaScript objects.

Install the dependency by running yarn add rss-parser.

Next let's add a function getFeed to our rss.js file.

lib/rss.js
import Parser from "rss-parser";

export const FEEDS = [ ... ];

export async function getFeed(feedUrl) {
  let parser = new Parser();

  let feed = await parser.parseURL(feedUrl);

  return feed;
}

We create a new parser instance and run the method parseUrl passing our feed url. All the rest is handled by rss-parser.

Now let's display our feed articles. Create a file feeds/[slug].js. It uses Next.js' dynamic routes.

With Next.js you can choose between different data fetching methods. We will build a static page. We start by writing our getStaticPaths function. We iterate over the hard coded RSS feeds. Because the values are hard coded we can set fallback to false as there is no way to add additional feeds without rebuilding our app.

pages/feeds/[slug].js
import { FEEDS } from "lib/rss";

export async function getStaticPaths() {
  return {
    paths: FEEDS.map((feed) => ({ params: { slug: feed.slug } })),
    fallback: false,
  };
}

Next let's add the getStaticProps function:

pages/feeds/[slug].js
import { FEEDS, getFeed } from "lib/rss";

...

export async function getStaticProps({ params }) {
  const feed = FEEDS.find((feed) => feed.slug === params.slug);
  const detailedFeed = await getFeed(feed.url);

  return {
    props: {
      feed,
      items: detailedFeed.items,
    },
    revalidate: 1,
  };
}

...

This function gets called for every valid path we defined earlier and gets passed our slug. We use this slug to lookup the corresponding RSS feed url and then fetch its items. We pass the feed and fetched items as props to our component.

As we cannot know when new articles are published we either would need to fetch them on every render with getServerSideProps or pass revalidate: 1 to enable Next.js' Incremental Static Regeneration. I'm personally a big fan of the possibilities it enables. We could also choose a larger revalidation interval if we like.

For our final step we display the articles in a list.

pages/feeds/[slug].js
import { FEEDS, getFeed } from "lib/rss";
import { format } from "date-fns";

export default function Feed({ feed, items }) {
  return (
    <div className="px-6 py-12 max-w-xl mx-auto">
      <h1 className="font-bold text-5xl mb-12">{feed.title}</h1>
      <div className="space-y-4">
        {items.map((item) => (
          <a
            key={item.link}
            className="block p-4 border border-gray-200 hover:border-gray-500 rounded-lg"
            href={item.link}
            target="_blank"
            rel="noopener noreferrer"
          >
            <div className="font-bold">{item.title}</div>
            <div>{format(new Date(item.isoDate), "PPP")}</div>
          </a>
        ))}
      </div>
    </div>
  );
}

export async function getStaticPaths() {
  return {
    paths: FEEDS.map((feed) => ({ params: { slug: feed.slug } })),
    fallback: false,
  };
}

export async function getStaticProps({ params }) {
  const feed = FEEDS.find((feed) => feed.slug === params.slug);
  const detailedFeed = await getFeed(feed.url);

  return {
    props: {
      feed,
      items: detailedFeed.items,
    },
    revalidate: 1,
  };
}
Feed detail page

This concludes this short tutorial. We could combine different RSS feeds into one content stream and even add other sources like Youtube to build a simple Mailbrew clone in a future article.