I have been working on converting a massive WordPress news site to NextJS. It uses WordPress as Headless CMS and Nextjs’s ISR feature for all the blog posts. NextJS’s ISR feature is awesome but it does not support XML page. In this post, I’ll share how I created a dynamic RSS feed using Next.js SSR and SSR cache option.
The problem
Fetch the latest 20 posts from WordPress and generate an RSS feed using nextJS SSR also implement SSR cache so it does not hit our WordPress API for every request.
NextJS’s ISR feature is awesome and if it allowed XML page support it would solve all the problems but it does not so we have to look into SSR.
Basic RSS feed example
<?xml version="1.0" ?>
<rss
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:atom="http://www.w3.org/2005/Atom"
version="2.0"
>
<channel>
<title>Your Site Name</title>
<atom:link href="https://siteurl.com/feed.xml" rel="self" type="application/rss+xml" />
<link>https://siteurl.com</link>
<description>Site description</description>
<language>en-US</language>
<lastBuildDate>Latest post Date</lastBuildDate>
<item>
<title><![CDATA[ Post Title ]]></title>
<link>https://siteurl.com/post-url</link>
<pubDate>Post Publish Date</pubDate>
<guid isPermaLink="false">Unique post ID</guid>
<description><![CDATA[ post excerpt]]></description>
<content:encoded><![CDATA[ post content ]]></content:encoded>
</item>
</channel>
</rss>
This is an example of a typical RSS feed you can also learn more about RSS feeds on w3.org
Fetching RSS feed data from WordPress
getFeedsPosts.js
import featch from "./featch";
async function getFeedsPosts() {
const data = await featch(
`
query MyQuery {
posts(first: 20) {
nodes {
id
uri
title
date
excerpt
content
}
}
}
`
);
return data.posts.nodes;
}
export default getFeedsPosts;
featch.js
const axios = require("axios").default;
export default async function featch(query, { variables } = {}) {
try {
const res = await axios({
url: process.env.NEXT_PUBLIC_WORDPRESS_API_URL,
method: "post",
data: {
query,
variables,
},
});
const { data } = res.data;
return data;
} catch (error) {
throw new Error("Failed to fetch API");
}
}
As you can see I’m just fetching the latest 20 posts data. Now we also need a helper function that will take this data and return XML content.
postsTofeed.js
export default async function postsTofeed(blogPosts) {
let latestPostDate = "";
let rssItemsXml = "";
blogPosts.forEach((post) => {
const postDate = Date.parse(post.date);
// Remember to change this URL to your own!
const postHref = process.env.NEXT_PUBLIC_BASE_URL + post.uri;
if (!latestPostDate || postDate > Date.parse(latestPostDate)) {
latestPostDate = post.date;
}
rssItemsXml += `
<item>
<title><![CDATA[${post.title}]]></title>
<link>${postHref}</link>
<pubDate>${new Date(post.date).toUTCString()}</pubDate>
<guid isPermaLink="false">${postHref}</guid>
<description>
<![CDATA[${post.excerpt}]]>
</description>
<content:encoded>
<![CDATA[${post.content}]]>
</content:encoded>
</item>`;
});
return {
rssItemsXml,
latestPostDate,
};
}
The above function returns latestPostDate and XML version of our RSS feed. You can learn more in depth about from Rob Kendal’s post in dev.to
Let’s create the feed.xml page
pages/feed.xml.js
import getFeedsPosts from "@/lib/wordpress/getFeedsPosts";
import postsTofeed from "@/lib/wordpress/postsTofeed";
const FeedPage = () => null;
function Feeds(feed) {
const { rssItemsXml, latestPostDate } = feed;
// console.log(totalPosts);
return `<?xml version="1.0" ?>
<rss
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:atom="http://www.w3.org/2005/Atom"
version="2.0"
>
<channel>
<title>Your Site Name</title>
<atom:link href="${
process.env.NEXT_PUBLIC_BASE_URL
}/feed.xml" rel="self" type="application/rss+xml" />
<link>${process.env.NEXT_PUBLIC_BASE_URL}</link>
<description>Your site description</description>
<language>en-US</language>
<lastBuildDate>${new Date(
latestPostDate
).toUTCString()}</lastBuildDate>
${rssItemsXml}
</channel>
</rss>`;
}
// This gets called on every request
export async function getServerSideProps({ res }) {
// Fetch data from external API
const posts = await getFeedsPosts();
const feed = await postsTofeed(posts);
//Set page headers
res.setHeader("Content-Type", "text/xml; charset=utf-8");
//Set cache for 600s so it wont call our wp on every request.
res.setHeader("Cache-Control", "s-maxage=600, stale-while-revalidate");
res.write(Feeds(feed));
res.end();
// Pass data to the page via props
return { props: {} };
}
export default FeedPage;
That’s it now you can go to http://localhost:3000/feed.xml to check your RSS feed.
Important notes
If you check my code I have used .toUTCString()
in many places its very important you do so because RSS feed works with Date and Time Specification of RFC 822. Also its best to set cache for SSR pages but not required.