ru
How to create RSS feed in Nuxt 3

How to create RSS feed in Nuxt 3

What is RSS

It is an XML file used by websites to receive information about page updates and new articles. The format is readable in browsers, as well as in special native or online RSS readers.

I use RSS feeds on all my sites and, although this format has lost some of its popularity, I know many folks who still use it. I am also among them.

Connecting RSS to Nuxt

As always, I wanted the magic to just plug and forget, but in my research I didn't find a working library for Nuxt, so I decided to use the node-rss library.

The pros of this solution is you can easily customize RSS to your needs. For example, add a second feed for an alternative language, or filter out those pages that are not needed in the feed.

Install node-rss.

npm i -D rss

And if the project uses TypeScript, as in my case, set types to it.

npm i -D @types/rss

Nuxt Configuration

For RSS feeds, we will require a page prenderer. Therefore, in the nuxt.config.ts file, in the Nitro section, specify a route for each RSS feed on the site.

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

Creating an article

In order to send information about page updates in the proper format, we need to include metadata in YAML format, the so-called frontmatter, in the body of each page. For example, an article file /content/posts/1.my-article.md may contain the following fields:

---
title: Article title
description: Article description
createdAt: 2023-09-01
---

Obviously, the frontmatter can contain other data, such as image file name, article topic, time read, etc., but the title, description, and creation date fields are what is required to generate a valid RSS feed structure.

Creating a route

Now let's create a server-side route for the RSS feed. Suppose the URL for it will be https://sitename.com/rss.xml.

Add the following code to the file server/routes/rss.xml.ts:

import { serverQueryContent } from '#content/server'
import RSS from 'rss'

interface RSSItem {
  title: string
  description: string
  createdAt: string
  _path: string
}

const hostname: string = 'sitename.com'

export default defineEventHandler(async (event) => {
  const feed = new RSS({
    title: 'Sitename',
    description: 'RSS feed for this site',
    site_url: hostname,
    feed_url: `${hostname}/rss.xml`,
  })

  const docs = await serverQueryContent(event)
    .sort({ createdAt: 1 })
    .where({ _partial: false })
    .locale('en')
    .only(['title', 'description', 'createdAt', '_path'])
    .find()

  const posts = docs.filter((doc) => doc?._path?.includes('/posts'))

  for (const post of posts as RSSItem[]) {
    feed.item({
      title: post.title ?? '-',
      description: post.description,
      date: post.createdAt,
      url: `${hostname}${post._path}`,
    })
  }

  const feedString = feed.xml({ indent: true })
  event.node.res.setHeader('content-type', 'text/xml')
  event.node.res.end(feedString)
})

There we use the asynchronous function serverQueryContent to get data about existing articles. And there we also specify the criteria for filtering (you can see the list of all filtering options in the documentation in the link at the end of the article).

In my case, I get only three fields from each English article in ascending date order.

Then I additionally filter the pages by the directory they are in.

And finally, I put the data into the node-rss library parser to generate the final source code for the page.

Feed validation

Once the RSS feeds have been created, you should definitely check them for correctness using an online validator. And, of course, load the feed into your favorite RSS client to make sure everything is working properly.

Summary.

I created two RSS feeds on my site: one for the English and one for the Russian version. They are located at different URLs and the only difference is the current locale filter .locale('en').

For the locale filter to work in Nuxt Content, it needs to be configured correctly. I will write about it next time.


References:

Last Updated: