September 21, 2022
Adding an RSS feed to a Next.js site
Really Simple Syndication (RSS) has been a pretty popular format for syndicating and publishing content on the web. Still, platforms don’t usually provide an out-of-the-box solution to adopt it.
For Next.js projects, specifically, I’m sure there are probably thousands of
articles explaining how to do it (and probably a handful of npm
packages,
too!), but in this post, I will try to outline my own approach to this problem
to add RSS capabilities for this very same blog.
How to build the RSS feed
The only thing I didn’t want to do was study the
RSS or
Atom spec myself to know how to
create a valid document. For that reason, I browsed the web looking for a
well-maintained npm
package that could help me externalize part of the work.
jpmonette/feed
ticked all the boxes (some
recent commits, a good amount of stars, and TypeScript support), so I installed
the package and started coding the RssFeedGenerator
class.
The RssFeedGenerator
is a simple TypeScript class that receives an array of
paths and gets all the posts that live there. Then, it makes minor adjustments
(flat the collection of posts, order by date) and uses the jpmonette/feed
package to return a valid atom feed string. Since this blog is open-sourced, you
may see the actual code for this class in
codecoolture/codecoolture.com
.
export class RssFeedGenerator {
constructor(private paths: string[]) {}
public async generate(): Promise<string> {
const feed = new Feed({
/* Add some feed configuration */
});
const allArticles = await Promise.all(
this.paths.map(async (path) => {
const repository = await MarkdownRepository.fromDirectory(path);
return repository.all({ drafts: false });
}),
);
const articlesFromNewestToOldest = orderBy(allArticles.flat(), "date", [
"desc",
]);
for (const article of articlesFromNewestToOldest) {
feed.addItem({
/* Add properties for each item */
});
}
return feed.atom1();
}
}
The RssFeedGenerator
does not know by itself what the paths are, so it expects
them to be injected during initialization. This decision was driven by my
approach using test-driven development since I wanted to create different
fixtures to test different scenarios without relying on the actual production
data (posts may change title, date, or even get deleted!).
Design-wise, injecting the RSS feed generator would also be desirable, so we could rely on our own abstractions rather than having this code coupled to a third-party library. Still, the use case is so simple that I decided to skip the extra work (I hope it does not bite me in the future!).
How to integrate RssFeedGenerator
with Next.js
At this point, this site uses next export
to build a static website.
Therefore, I couldn’t rely on some of the more advanced Next.js features such as
API Routes or even
middlewares. So instead,
I decided to integrate the RSS feed generation within the Next.js build by
adding a new npm script
that executes bin/rss/index.ts
(a TypeScript script)
and creates a RssFeedGenerator
instance with the right content paths, using
its output to save a file inside the Next.js public/
folder. This is what the
script and the new build command look like.
- "build": "next build",
+ "build": "yarn build:rss && next build",
+ "build:rss": "NODE_ENV=development yarn ts:exec ./bin/rss/index.ts",
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { getConfig } from "../../config";
import { RssFeedGenerator } from "./RssFeedGenerator";
(async function run() {
const generator = new RssFeedGenerator([
getConfig().writing.articles,
getConfig().writing.notes,
]);
const feed = await generator.generate();
await writeFile(join(__dirname, "../../public/feed.xml"), feed);
})();
With all these pieces now working together, new deployments of this site will
always create a valid RSS feed that can be found at /feed.xml
,
keeping the static nature of the website and leveraging a more sophisticated
build process.
What do you think if we make good use of this new feature and subscribe to the RSS feed? 😃