Today, I released a new version of my website with only one new feature: syndication through RSS, Atom and JSON Feed. Even though by some accounts, RSS seems to be dead, I strongly believe RSS is an important feature in the fight to keep ownership over your own content while also increasing exposure. The way I approach publishing and sharing my content, is called POSSE:
Publish (on your) Own Site, Syndicate Elsewhere.
RSS is an important part of that strategy.
Luckily, I'm not the only one who values RSS. There are others
In this article I want to share how I implemented syndication feeds in my NextJS-powered website.
Options and Requirements
I did some Googling to see how other NextJS users were generating RSS feeds, and there turned out to be many people that had solved this particular problem and wrote about it I found a couple of different approaches to generating and rendering RSS feeds in NextJS:
Generating feeds:
- Hand-build the required XML using templated strings 🤢
- Use a library to do it for you. The most popular library for this in the JS ecosystem seems to be feed 🤩
Rendering feeds:
Advantages:
- Create dynamic feeds (more on that later) that can be different for each visitor
- Using NextJS pages to represent feeds feels natural in the NextJS way of doing things
Disadvantages:
- You can't generate the website statically anymore (at least parts of it remain dynamic), which reduces performance As a side note: from a development perspective, server-side rendered pages and statically generatedpages in NextJS are so similar that the difference doesn't matter
Advantages:
- Because the feeds are generated at build time, the site remains snappy
Disadvantages:
- You cannot set the
Content-Type
header for statically generated pages, so you can't serve those pages asapplication/rss+xml
. I'm not how big of a problem this is and what black magic Vercel applies when serving my NextJS site
Picking the requirements
After looking at the options, I decided on the following requirements for my feeds:
- I want to go for statically generated feeds, to keep my site fast and the implementation simple
- Support for RSS, Atom and JSON Feed
- I want to include the complete blog post contents in the feeds. This is an important one because I personally really hate it when my RSS reader only shows me a summary of a post and I have to go the the website to read all of it. Caveat though: my website is built using MDX, so I might include components in the future that are not easily convertible to static HTML without Javascript enabled. In that case, readers will have to click through to my site.
Implementation
As per my requirements, I wanted to generate the feeds at build time. But as mentioned before, NextJS doesn't support setting the Content-Type
header for statically generated pages. The alternative that many people use, is to have a separate script generate your feeds and writing them to the public folder where all other static assets such as images are stored as well. That way, the feeds would be served as static assets instead of statically generated pages -- which, from the browsers perspective doesn't make a difference!
I found a good explanation by Ashlee Boyer of this technique.
My plan:
- Write script to generate feeds, using the feed library from NPM
- Run this script as a
postbuild
step so it would always be invoked after building the site usingnpm run build
(this happens not only locally, but also when Vercel deploys my site)
Problem 1: running Typescript modules is hard
I immediately hit a snag with (1), because I couldn't manage to use ES6/Typescript modules in a script run outside of my normal website code.
I'm using Typescript, and apparently ts-node
, the tool to run Typescript files, doesn't support modules. Writing the script in Javascript wasn't really an option for me because I wanted to reuse a lot of logic that I already wrote for reading and parsing MDX files in the website itself.
Solution
I decided to follow the route that Ashlee Boyer suggests in her blog post and sneak in the function to generate my feeds as a "stowaway" in the getStaticProps
function of my index page. This works beautifully!
export const getStaticProps: GetStaticProps = () => {
generateMainFeeds();
const lastPosts = getAllPostsFrontMatter('blog', 3);
return {
props: {
lastPosts,
},
};
};
Problem 2: including the full content in the feeds
The code of my website already supported translating MDX files into valid JSX to be rendered by React. But how to generate valid HTML from that content and include it in the feeds?
Solution
I couldn't find many examples of this, but did find out about ReactDOMServer.renderToStaticMarkup
. This function will take a bunch of React components and render them into HTML. This is what is used by many React server-side rendering solutions (maybe also by NextJS?) and works perfectly here as well.
One caveat: if your content contains internal links -- which are often relative links -- then you have to be mindful that those relative links are meaningless in the context of an RSS feed. The way I solved this is by doing some regex-based replacements on the generated HTML.
The complete content generation part looks like this:
const url = `${baseUrl}/blog/${post.frontMatter.slug}`;
const htmlContent = ReactDOMServer.renderToStaticMarkup(
<ChakraProvider resetCSS theme={theme}>
<MDXRemote {...post.mdxSource} components={MDXComponents} />
</ChakraProvider>
.replace(/href="\/#/g, `href="${url}#`)
.replace(/href="\//g, `href="${baseUrl}/`)
.replace(/src="\//g, `src="${baseUrl}/`);
)
Problem 3: getting rid of style information
My site uses Chakra UI for theming, which uses Emotion -- a CSS-in-JS library -- under the hood. Emotion will happily render tons of <style>
tags when statically generating HTML from your React components. For most use cases where you render React on the server (statically or not), this is desirable. In the case of RSS/Atom feeds, this is pretty useless.
Solution
The solution here is to strip all the <script>
and <style>
tags from the generated HTML. Rather than summoning The One whose Name cannot be expressed in the Basic Multilingual Plane by trying to use regex here, I found this library to help me with this task:
const cleanHtmlContent = stripHtml(htmlContent, {
onlyStripTags: ['script', 'style'],
stripTogetherWithTheirContents: ['script', 'style'],
}).result;
The end result
I now have serve RSS, Atom and a JSON Feed for your reading pleasure. Most of the relevant code can be found here
Future plans
At some point I want to diversify my writing output by not only writing about tech. And even within the topic of tech there are many sub-topics I could write about, not all of which are equally interesting to every reader (all 5 of them, including my mom 👩‍👦). I'm planning to introduce tags to allow filtering content once I have enough of it.
Once I have tags, I would like to start supporting dynamic feeds so readers can subscribe only to the stuff they actually want to read. I imagine building an endpoint like this:
/feeds/by-tags.xml?tags=tag1,tag2
I'm curious if others are doing this!
Top comments (0)