a colorful abstract background with wavy lines

Super easy RSS feeds with Next.js

In this tutorial, we're taking one of the new features in Next.js 13 - Route Handlers - and making an RSS feed with them, super easily!

Next.js 13 brings a lot of new features that streamline the experience of developing full-stack web apps. Let’s take a look at creating an RSS feed from your existing content.

In our example we’re going to be working with a blog but the same steps apply for any kind of RSS feed you may be creating (for example, a podcast). You would only need to change where your content is coming from and a few values in the feed.

Let’s start by creating a new folder in the app directory of our project. The name of this folder will serve as the location of our RSS feed. For example src/app/feed = http://localhost:3000/feed

Inside this folder make a new file called route.ts. The name here is important, as this will be picked up by Next.js as an entry point to the API call which will render the RSS feed.

Let’s also install some dependencies so we have everything we need. Run the following command in your terminal:

yarn add rss
yarn add @types/rss --dev #run this as well to get the Typescript types
yarn add rss
yarn add @types/rss --dev #run this as well to get the Typescript types

Add this import statement at the top of the route.ts file:

src/app/feed/route.ts
import RSS from "rss";
src/app/feed/route.ts
import RSS from "rss";

We’re fetching data in this file so our API method is going to be GET. We’ll export a GET function in the same file:

export async function GET() {
  //...
}
export async function GET() {
  //...
}

Now we’ll import all our blog posts (or as many as you want to include in the RSS feed).

const posts = await fetchAllPosts(); // replace this with your method for fetching data
const posts = await fetchAllPosts(); // replace this with your method for fetching data

Setting up the RSS feed info is easy thanks to the package we installed earlier. Visit the docs if you want more info on each option, but for now we’ll keep things simple. Add this underneath the post fetching:

var feed = new RSS({
  title: "My RSS Feed",
  description: "My feed all about my blog!", //optional
  feed_url: "http://localhost:3000/feed",
  site_url: "http://localhost:3000",
  copyright: `${new Date().getFullYear()} My Blog`,
  language: "en", //optional
});
var feed = new RSS({
  title: "My RSS Feed",
  description: "My feed all about my blog!", //optional
  feed_url: "http://localhost:3000/feed",
  site_url: "http://localhost:3000",
  copyright: `${new Date().getFullYear()} My Blog`,
  language: "en", //optional
});

Our feed now needs to be populated with items from our blog posts. You’ll probably want to make sure they’re sorted beforehand by date order. Set up an iteration to go through each blog post and render out the relevant data.

posts.forEach((post) => {
  feed.item({
    title: post.title,
    description: post.description,
    url: post.url,
    categories: post.tags, //optional
    author: post.author, //optional
    date: post.pubDate,
    custom_elements: [
      {
        "content:encoded": {
          _cdata: post.html, // or you could put a HTML link to your own website instead
        },
      },
    ],
  });
});
posts.forEach((post) => {
  feed.item({
    title: post.title,
    description: post.description,
    url: post.url,
    categories: post.tags, //optional
    author: post.author, //optional
    date: post.pubDate,
    custom_elements: [
      {
        "content:encoded": {
          _cdata: post.html, // or you could put a HTML link to your own website instead
        },
      },
    ],
  });
});

All that’s left for us to do now is return the XML data in a new Response object:

return new Response(feed.xml({ indent: true }), {
  headers: { "Content-Type": "application/atom+xml; charset=utf-8" },
});
return new Response(feed.xml({ indent: true }), {
  headers: { "Content-Type": "application/atom+xml; charset=utf-8" },
});

Now visit http://localhost:3000/feed to see your nicely formatted, type-safe, fully detailed RSS feed! 🎉