Adding RSS Feeds to a Next.js Static Blog

A step-by-step guide on how to add RSS, Atom, and JSON feeds to a Next.js static blog using the Feed package

Adding RSS Feeds to a Next.js Static Blog

This blog now supports RSS feeds! Here's how we implemented it using the feed package with Next.js static export and Velite for content management.

Dependencies

First, we added the required dependencies to our web app:

yarn workspace web add feed esbuild-register

RSS Generator

We created an RSS generator script that uses Velite's blog data to generate the feeds:

import { Feed } from "feed";
import fs from "fs";
import path from "path";
import { blogs } from "./.velite";
 
async function generateRssFeed() {
  const site_url = process.env.NEXT_PUBLIC_SITE_URL || "https://subaud.io";
 
  const feed = new Feed({
    title: "Court Schuett's Blog",
    description: "Technical blog posts about AWS, CDK, and web development",
    id: site_url,
    link: site_url,
    language: "en",
    favicon: `${site_url}/favicon.ico`,
    copyright: `All rights reserved ${new Date().getFullYear()}`,
    author: {
      name: "Court Schuett",
      link: site_url,
    },
  });
 
  blogs
    .filter((post) => post.published)
    .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
    .forEach((post) => {
      feed.addItem({
        title: post.title,
        id: `${site_url}/blog/${post.slugAsParams}`,
        link: `${site_url}/blog/${post.slugAsParams}`,
        description: post.description,
        content: post.description,
        author: [
          {
            name: "Court Schuett",
            link: site_url,
          },
        ],
        date: new Date(post.date),
        category: post.categories?.map((cat) => ({ name: cat })),
      });
    });
 
  // Write to both public and out directories
  const directories = [
    path.join(process.cwd(), "public"),
    path.join(process.cwd(), "out"),
  ];
 
  for (const dir of directories) {
    // Create directory if it doesn't exist
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir, { recursive: true });
    }
 
    // Write feed files
    fs.writeFileSync(path.join(dir, "rss.xml"), feed.rss2());
    fs.writeFileSync(path.join(dir, "feed.json"), feed.json1());
    fs.writeFileSync(path.join(dir, "atom.xml"), feed.atom1());
  }
}
 
generateRssFeed();

Build Process

We updated our build script in package.json to generate the RSS feeds during build:

{
  "scripts": {
    "build": "yarn velite build && next build && yarn rss:build",
    "rss:build": "node -r esbuild-register rss.ts"
  }
}

RSS Feed Page

We created a dedicated page for the RSS feeds at /rss that provides links to all available feed formats:

import Link from "next/link";
import { Rss } from "lucide-react";
import PageHeader from "@/components/page-header";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
 
export default function RSSPage() {
  return (
    <div className="container relative max-w-4xl py-6 lg:py-10">
      <PageHeader
        title="RSS Feeds"
        description="Subscribe to subaud using your favorite RSS reader"
      />
      <hr className="my-8" />
      <div className="prose dark:prose-invert">
        <h2>Available Feeds</h2>
        <div className="space-y-4">
          <div className="flex items-center space-x-2">
            <Rss className="h-4 w-4" />
            <Link
              href="/rss.xml"
              className={cn(buttonVariants({ variant: "link" }), "pl-0")}
            >
              RSS Feed
            </Link>
            <span className="text-muted-foreground">(RSS 2.0 format)</span>
          </div>
          <div className="flex items-center space-x-2">
            <Rss className="h-4 w-4" />
            <Link
              href="/atom.xml"
              className={cn(buttonVariants({ variant: "link" }), "pl-0")}
            >
              Atom Feed
            </Link>
            <span className="text-muted-foreground">(Atom 1.0 format)</span>
          </div>
          <div className="flex items-center space-x-2">
            <Rss className="h-4 w-4" />
            <Link
              href="/feed.json"
              className={cn(buttonVariants({ variant: "link" }), "pl-0")}
            >
              JSON Feed
            </Link>
            <span className="text-muted-foreground">(JSON Feed format)</span>
          </div>
        </div>
      </div>
    </div>
  );
}

We added a "Feed" link to the main navigation by updating our NAV_LIST constant:

export const NAV_LIST = [
  { label: "Search", path: "/blog", icon: Search },
  { label: "Categories", path: "/categories", icon: Icons.categories },
  { label: "Feed", path: "/rss", icon: Icons.rss },
  // ... other navigation items
];

Available Formats

The blog now provides feeds in three formats:

  • RSS 2.0 (/rss.xml)
  • Atom 1.0 (/atom.xml)
  • JSON Feed (/feed.json)

These feeds are automatically generated during the build process and are available as static files in the exported site.

Conclusion

By implementing RSS feeds, we've made it easier for readers to stay updated with new blog posts using their preferred feed reader. The feeds are generated statically during build time, which keeps our site fast and simple while providing this essential functionality.