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? 😃

Do you have any comments or doubts? We may continue the conversation on