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
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
package and installed it as a dev dependency.
Installation
# 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:
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.
// ...
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.
// ...
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.
// ...
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.
// ...
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.
// ...
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.
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 if you're interested in generating dynamic or server-side sitemaps.
Installation
# 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
module.exports = {
siteUrl: `https://${ process.env.VERCEL\_URL }`,
generateRobotsTxt: true,
};
And then in my package.json
, I added next-sitemap
to the postbuild
script.
{
"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! 🎉🎉🎉
Top comments (0)