RSS Feeds in Nuxt 3

low angle photography of black transmission tower
Photo by Markus Spiske on Unsplash
6 min readApr 17, 2024technology

We can integrates our Nuxt Content posts into RSS feeds through Nuxt Server routes with the help of rss npm package. Here I will show the simple way to do it.

Setup

First, make sure we have Nuxt 3 project and Nuxt Content installed. I will not show the details of how to install them, but we can refer to the official documentation for Nuxt 3 and Nuxt Content. As we know, with Nuxt Content we can create a document-driven blog site with markdown files as the source of our blog. Just remember the markdown frontmatter here:

md
---
author: Riza Afandi
title: How I built this site
description: Technical description how I built https://rizaafandi.me/
_draft: false
image: ONuLIzB0UtA
tag: technology
published: 4/05/2024 14:30
published_at: Central Java
---

We will use some of our markdown frontmatter setting in our RSS.

Then we need to install rss npm package into our project to generate the XML-based RSS feeds based on our content. We can install it with npm:

sh
npm install rss

Execution

Before the execution, lets add some keys in the .env file or you can create the file first in the root directory if it doesn't exists. We will use them in our RSS.

properties
PUBLIC_URL=https://ourwebsite.com
PUBLIC_TITLE=Our Website
PUBLIC_DESCRIPTION=Our Website Description

We must create a file inside the server route in our Nuxt project to put function for generating the RSS feeds, the reason we create it in there is because we will tell Nuxt to generate the RSS feeds from the server not in the client (browser). Lets create a rss.xml.ts file in /server/routes directory:

dir
├── server
|  └── routes
|  |   ├── rss.xml.ts

The rss.xml.ts file will be transformed into /rss.xml route by Nuxt.

Lets dive in to our rss.xml.ts file. The whole code is actually not that long, but lets walk into it step-by-step from the beginning. First we need to import from the rss package and #content/server dynamic import from Nuxt.

ts
import RSS from "rss";
import { serverQueryContent } from "#content/server";

Add default exported defineEventHandler function and wrap it in a try-catch block:

ts
...
export default defineEventHandler(async (event) => {
  try {
    ...
  } catch (e) {
    return e;
  }
});

Create some constant variables based on the .env file we created earlier to store our general information about our site:

ts
...
try {

    const URL = process.env.PUBLIC_URL?.toString() as string;
    const TITLE = process.env.PUBLIC_TITLE?.toString() as string;
    const DESCRIPTION = process.env.PUBLIC_DESCRIPTION?.toString() as string;
}
...

The next is we create some constants to get the data from our markdown content:

ts
...
    const docs = await serverQueryContent(event, "/thoughts").find();
    const feed = new RSS({
      title: TITLE,
      description: DESCRIPTION,
      site_url: URL + "/thoughts",
      feed_url: URL + "/rss.xml",
      language: "en",
      ttl: 30,
    });

    for (const doc of docs) {
      feed.item({
        title: doc.title?.toString() as string,
        url: `${URL}${doc._path?.toString() as string}`,
        date: new Date(doc?.published),
        description: doc?.description,
      });
    }

    const feedString = feed.xml({ indent: true });
...

Look at the script above. I created a docs constant to query our content from Nuxt Content. The serverQueryContent function is equivalent to queryContent, but it will run on the server routes. I pass event as the argument here, and in the second one, I pass "/thoughts" since I will only query the content from the /thoughts path. You can customize it depending on your content directory or leave it empty and only pass one argument so Nuxt will query all the content files.

The feed constant here is the object from the rss package and is configured with several properties, such as the title and description we defined earlier. The site_url property is a valid URL that refers to our blog URL, and feed_url is our RSS feed URL. The language is simply the language we use, and ttl is the time that RSS will cache in minutes from our server until refreshing from the source.

Next, we iterate over the docs constant that contains our content and store it in the feed constant we created earlier. Here we can manage the feed properties based on our content frontmatter (remember the first markdown properties we knew earlier in this post).

The last is that we need to transform our feed into an indented XML string.

Next step is we must set the header content-type of our server route into text/xml and returning our configured feed:

ts
...
    event.node.res.setHeader("content-type", "text/xml");
    event.node.res.end(feedString);
...

Here is the final content in rss.xml.ts file:

ts
import RSS from "rss";
import { serverQueryContent } from "#content/server";

export default defineEventHandler(async (event) => {
  try {
    const URL = process.env.PUBLIC_URL?.toString() as string;
    const TITLE = process.env.PUBLIC_TITLE?.toString() as string;
    const DESCRIPTION = process.env.PUBLIC_DESCRIPTION?.toString() as string;

    const docs = await serverQueryContent(event, "/thoughts").find();
    const feed = new RSS({
      title: TITLE,
      description: DESCRIPTION,
      site_url: URL + "/thoughts",
      feed_url: URL + "/rss.xml",
      language: "en",
      ttl: 30,
    });

    for (const doc of docs) {
      feed.item({
        title: doc.title?.toString() as string,
        url: `${URL}${doc._path?.toString() as string}`,
        date: new Date(doc?.published),
        description: doc?.description,
      });
    }

    const feedString = feed.xml({ indent: true });

    event.node.res.setHeader("content-type", "text/xml");
    event.node.res.end(feedString);
  } catch (e) {
    return e;
  }
});

Conclusion

After we defined the rss.xml.ts route file, we can go to /rss.xml in our browser URL and we can see Nuxt will generate the RSS feeds we configured:

xml
<?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[Riza Afandi]]></title>
        <description><![CDATA[I'm a software engineer from Indonesia and Computer Science student at Bina Nusantara University]]></description>
        <link>https://rizaafandi.me/thoughts</link>
        <generator>RSS for Node</generator>
        <lastBuildDate>Wed, 17 Apr 2024 07:53:38 GMT</lastBuildDate>
        <atom:link href="https://rizaafandi.me/rss.xml" rel="self" type="application/rss+xml"/>
        <language><![CDATA[en]]></language>
        <ttl>30</ttl>
        <item>
            <title><![CDATA[Lorem Ipsum: a beginning]]></title>
            <description><![CDATA[A brief explanation, a gentle reason how I am finally writing this site]]></description>
            <link>https://rizaafandi.me/thoughts/lorem-ipsum</link>
            <guid isPermaLink="true">https://rizaafandi.me/thoughts/lorem-ipsum</guid>
            <pubDate>Sat, 30 Mar 2024 05:30:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Create RSS Feeds in Nuxt 3 and Nuxt Content]]></title>
            <description><![CDATA[How to deliver our blog posts into RSS Feeds in Nuxt 3 and Nuxt Content]]></description>
            <link>https://rizaafandi.me/thoughts/rss-in-nuxt-3</link>
            <guid isPermaLink="true">https://rizaafandi.me/thoughts/rss-in-nuxt-3</guid>
            <pubDate>Wed, 17 Apr 2024 07:30:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[How I built this site]]></title>
            <description><![CDATA[Technical description how I built https://rizaafandi.me/]]></description>
            <link>https://rizaafandi.me/thoughts/this-site</link>
            <guid isPermaLink="true">https://rizaafandi.me/thoughts/this-site</guid>
            <pubDate>Fri, 05 Apr 2024 07:30:00 GMT</pubDate>
        </item>
    </channel>
</rss>

The very last step is we need to whitelisting our /rss.xml route rendering in the nuxt.config.ts file so Nuxt will pre-render this route at build time since the server won't be able to run on a static hosting

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

That's all, thank you for reading this post

Written in Jakarta

Updated yesterday

© 2024