Generating an RSS Feed and Sitemap in Next.js
Tags: nextjs, javascript, typescript
Published: 2021-12-18 01:13:50

---
title: Generating an RSS Feed and Sitemap in Next.js
published: true
date: 2021-06-30 05:35:00 UTC
tags: nextjs, javascript, js, typescript
canonical_url: https://kevinlangleyjr.dev/blog/generating-an-rss-feed-and-sitemap-in-next-js
---

With my new site being built with Next.js, I needed to figure out a way to still provide RSS feeds and a sitemap using the MDX files as a source. I use [`next-mdx-remote`](https://github.com/hashicorp/next-mdx-remote) from Hashicorp for the parsing and rendering of my MDX files and with the latest major version, v3, it got to be a little tricky so hopefully you'll find this helpful!

## RSS Feed

To generate an RSS feed for my Next.js site, I used the aptly named [`feed`](https://www.npmjs.com/package/feed) package and installed it as a dev dependency.

### Installation

```bash
# Using yarn
yarn add feed -D

# or using NPM
npm install feed --save-dev
```

### Implementation

I then created a file named `feeds.tsx` in my `utils` directory. And we'll start off with this:

```tsx
import fs from 'fs';
import { Feed } from 'feed';
import { serialize } from 'next-mdx-remote/serialize';
import { MDXRemote } from 'next-mdx-remote';
import { renderToStaticMarkup } from 'react-dom/server';
import { MDXComponents } from '@components/MDX';
import { getPosts } from '@utils/mdx';

const generateFeeds = async () => {

}
```

From there, I start with some basic data like the date, my name and author information, as well as the site url and I also fetch the posts which are just local MDX files in the `data` directory.

```tsx
// ...

const generateFeeds = async () => {
  const domain = process.env.NEXT_PUBLIC_MAIN_DOMAIN || process.env.VERCEL_URL;
  const posts = await getPosts();
  const siteURL = `https://${domain}`;
  const date = new Date();

  const author = {
    name: 'Kevin Langley Jr.',
    email: 'me@kevinlangleyjr.com',
    link: 'https://kevinlangleyjr.dev',
  };
}

// ...
```

Then I created a new Feed object.

```tsx
// ...

const feed = new Feed( {
  title: 'Kevin Langley | Web Engineer',
  description: '',
  id: siteURL,
  link: siteURL,
  image: `${siteURL}/kevinlangleyjr.jpg`,
  copyright: `All rights reserved ${date.getFullYear()}, Kevin Langley Jr.`,
  updated: date,
  generator: 'Next.js',
  feedLinks: {
    rss2: `${siteURL}/rss/feed.xml`,
    json: `${siteURL}/rss/feed.json`,
    atom: `${siteURL}/rss/atom.xml`,
  },
  author,
} );

// ...
```

Then we can finally start looping over our posts to add to the feed. With the latest changes to the `next-mdx-remote` package and no longer having the `renderToString` method in v3, we are using `renderToStaticMarkup` from `react-dom/server` to generate the markup to add to our feeds.

```tsx
// ...

for ( const post of posts ) {
  const url = `${siteURL}/blog/${post.slug}`;
  const { content, ...postData } = post;
  const mdxSource = await serialize( content, {
    scope: postData as Record<string, any>, // Scope expects a <Record> here.
    mdxOptions: {
      remarkPlugins: [
        // I use a few different remark plugins, check out my "How I built my blog" article for more information!
      ],
    },
  } );

  const markup = renderToStaticMarkup(
    <MDXRemote components={ MDXComponents } { ...mdxSource } />
  );

  feed.addItem(
    {
      title: post.title,
      guid: post.slug,
      id: url,
      link: url,
      description: post.excerpt,
      content: markup,
      author: [author],
 contributor: [author],
 date: new Date( post.published_date ),
    }
);

}

// ...
```

And then we can finally write those feeds to their appropriate files.


```tsx
// ...

fs.mkdirSync( './public/rss', { recursive: true } );
fs.writeFileSync( './public/rss/feed.xml', feed.rss2() );
fs.writeFileSync( './public/rss/atom.xml', feed.atom1() );
fs.writeFileSync( './public/rss/feed.json', feed.json1() );

// ...
```


Now, how do we get these feeds to automatically run on build?

Easy! Just call it in the `getStaticProps` method in the blog index and it will generate on build. I like to wrap it in a check for the `VERCEL` environment variable which will then only run when building on the Vercel platform, or if I pass in the environment variable when building locally.

```tsx
// ...

export const getStaticProps: GetStaticProps = async () => {
  const { posts, hasNextPage } = getPostsForPage();

  if ( process.env?.VERCEL ) {
    await generateFeeds();
  }

  return {
    props: {
      posts,
      hasNextPage,
    },
  };
};
// ...
```

Here is the entire `feed.tsx` file with all the steps completed.

```tsx
import fs from 'fs';
import { Feed } from 'feed';
import { serialize } from 'next-mdx-remote/serialize';
import { MDXRemote } from 'next-mdx-remote';
import { renderToStaticMarkup } from 'react-dom/server';
import { MDXComponents } from '@components/MDX';
import { getPosts } from '@utils/mdx';

const generateFeeds = async () => {

  const domain = process.env.NEXT_PUBLIC_MAIN_DOMAIN || process.env.VERCEL_URL;
  const posts = await getPosts();
  const siteURL = `https://${domain}`;
  const date = new Date();
  const author = {
    name: 'Kevin Langley Jr.',
    email: 'me@kevinlangleyjr.com',
    link: 'https://kevinlangleyjr.dev',
  };

  const feed = new Feed( {
    title: 'Kevin Langley | Web Engineer',
    description: '',
    id: siteURL,
    link: siteURL,
    image: `${siteURL}/kevinlangleyjr.jpg`,
    copyright: `All rights reserved ${date.getFullYear()}, Kevin Langley Jr.`,
    updated: date,
    generator: 'Next.js',
    feedLinks: {
      rss2: `${siteURL}/rss/feed.xml`,
      json: `${siteURL}/rss/feed.json`,
      atom: `${siteURL}/rss/atom.xml`,
    },
    author,
} );

for ( const post of posts ) {
  const url = `${siteURL}/blog/${post.slug}`;
  const { content, ...postData } = post;
  const mdxSource = await serialize( content, {
    scope: postData as Record<string, any>, // Scope expects a <Record> here.
    mdxOptions: {
      remarkPlugins: [
        // I use a few different remark plugins, check out my "How I built my blog" article for more information!
      ],
    },
  } );

  const markup = renderToStaticMarkup(
    <MDXRemote components={ MDXComponents } { ...mdxSource } />
  );

  feed.addItem(
    {
      title: post.title,
      guid: post.slug,
      id: url,
      link: url,
      description: post.excerpt,
      content: markup,
      author: [author],
 contributor: [author],
 date: new Date( post.published_date ),
    }
  );

  fs.mkdirSync( './public/rss', { recursive: true } );
  fs.writeFileSync( './public/rss/feed.xml', feed.rss2() );
  fs.writeFileSync( './public/rss/atom.xml', feed.atom1() );
  fs.writeFileSync( './public/rss/feed.json', feed.json1() );
}
```

## Sitemap

For the sitemap functionality, I chose to use `next-sitemap` which provides a simple API to create a sitemap from your static, dynamic, and server side pages in Next.js.

I'm only using static pages on my site, but you can check out the [README](https://github.com/iamvishnusankar/next-sitemap#generating-dynamicserver-side-sitemaps) if you're interested in generating dynamic or server-side sitemaps.

### Installation

```bash
# Using yarn
yarn add next-sitemap -D

# or using NPM
npm install -D next-sitemap
```

### Implementation

From there, I created a new file in the root of the project named, `next-sitemap.js` and I provided a basic configuration within. You can find the different configuration options supported in the [README](https://github.com/iamvishnusankar/next-sitemap#configuration-options)

```js
module.exports = {
 siteUrl: `https://${ process.env.VERCEL\_URL }`,
 generateRobotsTxt: true,
};
```

And then in my `package.json`, I added `next-sitemap` to the `postbuild` script.

```json
{
  "build": "next build",
  "postbuild": "next-sitemap"
}
```

From there, whenever you build your project you'll have a `sitemap.xml` and if you set `generateRobotsTxt` to `true` in the configuration, a `robots.txt` generated into your `public` directory.

Profit! 🎉🎉🎉