Adding an RSS Feed to your Nuxt 3 Site

I recently added an RSS feed to my site so visitors can subscribe to my latest posts using their preferred RSS readers. Prolific bloggers use it; people won't always have the time visit your site; and we usually read from multiple sources.

An RSS feed is normally included with publishing platforms like WordPress. With Nuxt 3, adding one isn't straightforward, so I wrote this guide to help others set up theirs.

This only covers prerendered static generated sites and markdown posts managed by Nuxt Content. However, I believe it's still a good starting point even with server-side rendering or other content backends.

Add a server route

Create an endpoint for the feed - let's define it as /rss.xml. The endpoint must return an XML response.

Add the file server/route/rss.xml.js with the following codes:

export default defineEventHandler(async (event) => {
event.res.setHeader('content-type', 'text/xml')
})

The route must be prerendered. On the nuxt.config.js file, add:

export default defineNuxtConfig({
nitro: {
prerender: {
routes: [ '/rss.xml' ]
}
}
})

Read more about server routes here.

Output the base RSS XML data

Add the RSS library using npm i rss, then update the server/route/rss.xml.js file to:

import RSS from 'rss'
const SITE_URL = 'your-site-url'
export default defineEventHandler(async (event) => {
const feed = new RSS({
title: 'My Site',
site_url: SITE_URL,
feed_url: SITE_URL + '/rss.xml',
})
event.res.setHeader('content-type', 'text/xml')
event.res.end(feed.xml({ indent: true }))
})

The endpoint should respond with data similar to:

<?xml version="1.0" encoding="UTF-8"?><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><![CDATA[Title]]></title>
<description><![CDATA[Description]]></description>
<link>http://localhost:3000</link>
<generator>RSS for Node</generator>
<lastBuildDate>{{ current date }}</lastBuildDate>
<atom:link href="http://localhost:3000/rss.xml" rel="self" type="application/rss+xml"/>
</channel>
</rss>

Retrieve and list blog files

Let's say you have your blog posts on Markdown files under the content/blog directory.

Update the server/route/rss.xml.js file to:

import RSS from 'rss'
import { serverQueryContent } from '#content/server'
const SITE_URL = 'your-site-url'
export default defineEventHandler(async (event) => {
const feed = new RSS({
title: 'My Site',
site_url: SITE_URL,
feed_url: SITE_URL + '/rss.xml',
})
const docs = await serverQueryContent(event)
.sort({ createdAt: -1 })
.find()
const blogPosts = docs.filter((doc) => doc?._path?.includes('/blog'))
// .slice(0, 10) // limit the output to 10 posts only
for (const doc of blogPosts) {
feed.item({
title: doc.title ?? '-',
url: `${SITE_URL}${doc._path}`,
date: doc.createdAt, // front matter date <- adjust
description: doc.description, // front matter data <- adjust
})
}
event.res.setHeader('content-type', 'text/xml')
event.res.end(feed.xml({ indent: true }))
})

Change the sort condition to the front matter field that you want to sort by. In this case, the posts all have a createdAt date fields. This sorts the posts in reverse chronological order.

Each post also has a description front matter field, which contains the description/summary of the article. This should also be changed depending on your setup.

The XML response should be like:

<?xml version="1.0" encoding="UTF-8"?><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><![CDATA[Title]]></title>
<description><![CDATA[Description]]></description>
<link>http://localhost:3000</link>
<generator>RSS for Node</generator>
<lastBuildDate>{{ current date }}</lastBuildDate>
<atom:link href="http://localhost:3000/rss.xml" rel="self" type="application/rss+xml"/>
<item>
<title><![CDATA[Title]]></title>
<description><![CDATA[Description]]></description>
<link>http://localhost:3000/blog/article</link>
<guid isPermaLink="true">http://localhost:3000/blog/article</guid>
<pubDate>{{ created date }}</pubDate>
</item>
...
</channel>
</rss>

At this point, you now have a working RSS feed. You can commit and deploy the changes.

Optional: Include the markdown content

Showing only the summary of posts on your feed is probably best for sites that need inbound traffic. Otherwise, including the whole article would be convenient for your readers.

RSS supports HTML elements inside the content:encoded tag. While Nuxt Content can transform markdown into HTML, you can only do that with the ContentRenderer component on a view template.

To convert markdown files into HTML from the server route codes, we'll use Showdown.

Install it with npm i showdown, then import the following:

import showdown from 'showdown'
import { join } from 'path'
import { readFile } from 'fs/promises'

Create a Showdown Converter instance inside the handler:

const converter = new showdown.Converter()

Inside the blog post loop, add:

for (const doc of blogPosts) {
const filename = join(process.cwd(), 'content', doc._file)
const html = converter.makeHtml(await readFile(filename, 'utf8'))

Each post object contains the file name (_file) relative to the content/ directory. The snippet above builds the absolute file name, reads the file, then generates the HTML string using the converter instance.

Static site markdowns normally have a front matter that should be removed:

const filename = join(process.cwd(), 'content', doc._file)
const markdownText = await readFile(filename, 'utf8')
let contentWithoutFrontmatter = markdownText
const frontmatterEndIndex = markdownText.indexOf('---', 3)
if (frontmatterEndIndex !== -1) {
contentWithoutFrontmatter = markdownText.slice(frontmatterEndIndex + 3).trim()
}
const html = converter.makeHtml(contentWithoutFrontmatter)

Then in the feed item method, add the HTML content:

feed.item({
title: doc.title ?? '-',
url: `${SITE_URL}${doc._path}`,
date: doc.createdAt,
description: doc.description,
custom_elements: [
{ 'content:encoded': { _cdata: html } }
]
})

That's it! Build your site and load the RSS feed on a reader app.

RSS Autodiscovery

Your visitors will need to know the exact URL of your feed to subscribe, but adding this tag will enable readers to automatically detect the feed:

<link rel="alternate" type="application/rss+xml" title="My Site" href="/rss.xml">

In Nuxt 3, define this under the app.head.link of your nuxt.config.js:

app: {
head: {
link: [
{
rel: 'alternate',
type: 'application/rss+xml',
title: 'My Site',
href: '/rss.xml'
}
]
}
}