How to add an RSS feed to a Next.js site
RSS feeds are a useful resource for being able to follow aggregated updates across all your favorite blogs, podcasts, and other digital medium. Thankfully, it's actually pretty easy to get set up, especially on a Next.js site.
Gathering Requirements
An RSS feed is just a special XML description of the content on the site. You can read the specification here if you're interested.
- Expose the feed(s) through a URL
- Add meta links in the header for RSS discoverability
Creating the RSS endpoints
The first decision is where to serve the RSS (and related feeds) from. After a little research I learned that Wordpress uses /feed
as the default route to serve the feed, which seems good enough for me. In that case, the routes I want to expose are:
GET /feed
,GET /feed/rss
→ RSS Feed
GET /feed/atom
→ Atom Feed
GET /feed/json
→ JSON Feed
You can choose different paths than this if you'd like, I don't think the actual route is that important.
These routes server non-html (data) payloads so in my mind they map well to Next.js API routes. Given that, I decided to create them under pages/api/feed/*
. That ultimately ends up with me having these files:
pages/api/feed/rss.ts
pages/api/feed/atom.ts
pages/api/feed/json.ts
Given how Next.js works though, the above API routes won't be at the path I want! The RSS feed will be at /api/feed/rss
and I want it to serve from /feed
. Thankfully in Next.js 9.5 the Vercel team introduced rewrites to solve for this.
Rewrites allow you to map an incoming request path to a different destination path. — Next.js Docs
// next.config.js
module.export = {
async rewrites() {
return [
{
source: "/feed",
destination: "/api/feed/rss",
},
{
// The /:slug part is a generic parameter handler to catch all other cases
source: "/feed/:slug",
destination: "/api/feed/:slug",
},
];
}
}
Setting up the feed
For expediencies' sake I chose to use the feed module to build out my feed and its different formats. This module gives a single, typed interface for exporting all the types of feeds that I'm interested in.
// lib/feed.ts
import { Feed } from "feed";
import { getPublishedPosts } from "./notion/blog";
export const buildFeed = async () => {
// This contains site level metadata like title, url, etc
const feed = new Feed({
// Global feed config
});
const posts = await getPublishedPosts();
posts.forEach((post) => {
feed.addItem({
// Individual post config
});
});
return feed;
};
It's worth noting that this feed configuration is actually shared between all the different formats. That means we can set up the feed once and have it generate rss, atom, and json feeds accordingly.
Connecting the feed to our routes
Now that we have the feed defined, let's hook up the api routes to return our feed data.
// pages/api/feed/rss.ts
import { NextApiRequest, NextApiResponse } from "next";
import { buildFeed } from "lib/feed";
export default async (req: NextApiRequest, res: NextApiResponse) => {
const feed = await buildFeed();
res.statusCode = 200;
res.setHeader("content-type", "application/rss+xml");
res.end(feed.rss2());
};
That's all it takes to serve the RSS feed! The only real work here is setting the header for the appropriate response type and calling the appropriate formatting function from the feed module. If this were an atom feed the content-type header value would be application/atom+xml
and the feed formatter feed.atom1()
. Likewise for the json feed we'd have application/feed+json
and feed.json1()
.
A note about content-type headers
There's actually a lot of different advice on what to set the content-type
header for these. The JSON feed header is most correct as listed on the spec site. While application/rss+xml
is technically the most correct MIME type for RSS, it's actually not the most compatible. If you try to open an RSS feed with that MIME type on Firefox for example, it prompts you to download the file instead of displaying it. W3 notes that for widest compatibility, it's okay to use application/xml
. That said, a lot of sites even use text/xml
. I'll likely end up updating mine to application/xml
, I just haven't got around to that yet.
Adding feed discoverability
There's typically two things you should do to make your feed discoverable. One is adding a link ref="alternative"
in the head which points to the feed. For Next.js this means using the next/head
component.
// pages/index.tsx
import Head from 'next/head'
const Blog = () => {
return (
<>
<Head>
<link
key="rss-feed"
rel="alternative"
type="application/rss+xml"
title="RSS feed for just-be.dev"
href="/feed"
/>
<link
key="atom-feed"
rel="alternative"
type="application/atom+xml"
title="Atom feed for just-be.dev"
href="/feed/atom"
/>
<link
key="json-feed"
rel="alternative"
type="application/feed+json"
title="JSON feed for just-be.dev"
href="/feed/json"
/>
</Head>
{/* other page content here */}
</>
)
}
Some browsers/extensions will give users indicators to subscribe to the feed when this is discovered. This will also give a hint to crawlers that a feed is available.
The other thing you should do to make your feed discoverable is pretty simple... add a link to it on your page! Josh Comeau has a subtle icon in the top level of his site that points to it.

I referenced his site because I don't have one yet 😄. I'll get one up eventually, after I figure out how to rework my menu..

I hope this was helpful! If you have any questions, hit me up on twitter @zephraph. Also feel free to checkout out the source to see exactly what I've done.