Generating Feeds with Next.js Route Handlers
Since I’ve started collecting notes and highlights here, I’ve been meaning to return them as formatted feeds, RSS being the main one. Well, I got around to it. It was way easier than I remembered, and I even got bonus Atom and JSON feeds out of it.
I’m using Next 13.2 and its new App Directory to generate the site, so this made feeds delightfully simple to implement. In fact, it may be the best experience I’ve ever had for developing content feeds like these. I want to share my walkthrough and results since this is a pretty common task when setting up a new project with Next, and all the existing examples were based in Next’s older pages
generation system.
How to Generate RSS, Atom, and JSON Feeds with Markdown content using Next.js App Directory Route Handlers
I started from the point of already having data-fetching functions for getting all my notes from my CMS (the aptly named getAllNotes
and getNoteTitle
).
When adding a new function to generate the feed, it simply has to set the top-level properties then run over the notes to add them as entries. I author and store all my notes as Markdown, so for each note I render its body into HTML. Each feed format then gets its own Route Handler, which calls the generator function for the formatted feed. Finally, I update the top-level metadata to include links to the newly added feeds.
Create a Site URL
I quickly realized I needed a little utility function to get the canonical site URL. Since I build and host using Vercel, I want to make sure my site URL corresponds with its preview deploy URL. I used a combination of environment variables to figure that out, using a dedicated SITE_URL
variable with Vercel’s system environment variables to figure out the build’s context and dedicated URL.
Render Markdown to HTML
To render Markdown into HTML, I used the unified
library with the plugins:
remark-parse
to parse the Markdown string into an ASTremark-rehype
to convert the Markdown into HTMLrehype-sanitize
to ensure the HTML is safe to renderrehype-stringify
to turn the AST back into a string
This string was then passed as the content
value for each feed item.
Create the Feed
With other site generation frameworks I’ve used, generating feeds has meant writing a template XML file and filling in dynamic values with curly-braced variables, usually with that format’s spec open alongside. This time, I was able to use the feed
package for all the XML authoring. As a result, generating multiple feed formats became a matter of making a function call.
The generateFeed
function is based on an example provided by Ashlee M Boyer. It creates a feed with proper metadata, then generates each post. Since the Markdown generation runs asynchronously, adding entries needs to happen inside a Promise.all
call. This way, generateFeed
waits to return the feed object until all content has finished generating.
Create the Feed Endpoints
Now here comes the fun part. Creating feed endpoints becomes so simple it’s silly. Using Route Handlers introduced in Next.js 13.2, adding a new endpoint is as simple as creating a folder in the App Directory with the name of the feed file, then creating a route.ts
file inside it.
So, to add the RSS feed, I create the folder src/app/feeds/rss.xml
and then create route.ts
inside it.
To create the Atom and JSON feeds, I follow the same process ensuring that the appropriate method and content type are used in the format’s route handler.
Adding alternates to site metadata
The last step is updating the site’s <head>
to reference these feeds to make them more discoverable to readers. This is made even easier using the App Directory’s Metadata API—also new to Next.js 13.2. In the top-most page
or layout
file in my app
directory, I add an alternates
property to the exported metadata
object:
That’s it!
Now after running next dev
, I can see I have feed files generated at /feeds/rss.xml
, /feeds/atom.xml
, and /feeds/feed.json
. I’ve gotten feeds in three different formats with only a few libraries and simple, easily testable functions.
After deploying to production, you can now follow my new notes via:
The flourishing, decentralized Web
The level of productivity I feel when using Next.js, Vercel, and GitHub together is really hard to beat. It feels like the tools are getting out of my way and letting me developer smaller PRs faster.
I’m still a daily RSS user. It’s my preferred way to read on the web. I’m glad to see that there’s still robust library support for RSS and feed generation, at least within the Node ecosystem at least. I don’t think RSS is going anywhere, especially since it powers the entire podcasting ecosystem. It’s great to see the longevity of these open standards.
Speaking of open standards, integrating an ActivityPub server into a Next.js application is something I’m interested in exploring next. It’d be very cool to have a site generated out of an aggregation of one’s own ActivityPub feeds, for example combinining posts from personal micro.blog, Mastodon and Pixelfed into a single syndicated feed.
Seeing all of the recent progress in decentralizing important services has felt so cool. We can still keep the Web wild and weird, empower individuals with more tools for expressing themselves online, and have it all be user-friendly. Content feeds are an important force for good here, so I’m very glad how easy it is these days for even a novice developer to publish them.