This page generates an RSS feed at build time with Next.js

Hello there 👋🏼 You found the secret page that generates the whitep4nth3r.com RSS feed at build time! So, how does it work?

This site is built with Next.js. At build time, we use getStaticProps() to fetch the blog post content from Contentful, construct a string of XML, and write it to this XML file in the Next.js public directory, which serves static files.

This site is hosted with Vercel. I set up a webhook in the Contentful space that pushes a deploy command to Vercel each time a change is made to the content. This means that each time content is added or updated in Contentful, a new RSS feed file is automatically generated. 😎

A quick look at the code

View the full code file on GitHub here.

The ContentfulApi class is a bespoke class I constructed to query data from Contentful via the Contentful GraphQL API. View the code here.

1. Fetch all blog posts

javascript
import ContentfulApi from "@utils/ContentfulApi";

export default function buildRss(props) {

  return (
    //...
  )
}

export async function getStaticProps() {
  const posts = await ContentfulApi.getAllBlogPosts();

  //...
}

2. Construct the string of XML, iterating over the blog post data to build the feed items

javascript
import ContentfulApi from "@utils/ContentfulApi";

export default function buildRss(props) {

  return (
    //...
  )
}

function buildRssItems(posts) {

  // This is the minimum data required for an RSS feed item

  return posts
    .map((post) => {
      return `
        <item>
          <title>${post.title}</title>
          <description>${post.excerpt}</description>
          <author>${Config.site.email} (${Config.site.owner})</author>
          <link>https://${Config.site.domain}/blog/${post.slug}</link>
          <guid>https://${Config.site.domain}/blog/${post.slug}</guid>
          <pubDate>${post.date}</pubDate>
        </item>
        `;
    })
    .join("");
}

export async function getStaticProps() {
  const posts = await ContentfulApi.getAllBlogPosts();

  const feedString = `<?xml version="1.0"?>
    <rss version="2.0" 
      xmlns:atom="http://www.w3.org/2005/Atom">
      <channel>
        <title>${Config.site.title}</title>
        <link>https://${Config.site.domain}</link>
        <atom:link 
          href="https://${Config.site.domain}/feed.xml" 
          rel="self" 
          type="application/rss+xml" />
        <description>${Config.site.feedDescription}</description>

        ${buildRssItems(posts)}

    </channel>
    </rss>`;

  //...
}

3. Write the string to feed.xml in the public directory

javascript
import ContentfulApi from "@utils/ContentfulApi";
import fs from "fs";

export default function buildRss(props) {
  // we can return null here if we don't want to
  // show any content on this page
  return null
}

export async function getStaticProps() {

 //...

 const feedString = ...

  fs.writeFile("./public/feed.xml", feedString, function (err) {
    if (err) {
      console.log("Could not write to feed.xml");
    }

    console.log("feed.xml written to ./public!");
  });

  return {
    props: {
      feedString, // return some data to the page, even if we don't use it
    },
  };
}

If you don't want to display any content on /buildrss, you can return `null` from the page function.

4. Bonus content! Make the RSS feed auto-discoverable

Add the following snippet to the <head> of your page to allow RSS readers to automatically discover the feed.

markup
<link
  rel="alternate"
  type="application/rss+xml"
  title="RSS Feed for whitep4nth3r.com"
  href="https://whitep4nth3r.com/feed.xml"
 />

✨ And that's how the RSS feed for this website is created!

I built this functionality live on Twitch. If this top secret article has been useful for you, I'd love for you to come and say hi when I'm live!

And remember: Build stuff, learn things, love what you do.