DEV Community

Cover image for Create an RSS Feed With Nuxt 3 and Nuxt Content v2
Michael Hoffmann
Michael Hoffmann

Posted on • Originally published at mokkapps.de on

Create an RSS Feed With Nuxt 3 and Nuxt Content v2

My portfolio website is built with Nuxt 3 and Nuxt Content v2. An RSS feed with my latest five blog posts is available here. In this article, you'll learn how to add an RSS feed to your Nuxt website.

Setup

First, let's create a new Nuxt 3 project. As the next step, we need to add the Nuxt Content v2 module to our application.

Finally, let's add some content that will be included in the RSS feed:

├── content
|  └── blog
|  └── blog
|  |   ├── article-1.md
|  |   ├── article-2.md
|  |   ├── article-3.md
|  |   ├── article-4.md
|  |   ├── article-5.md
Enter fullscreen mode Exit fullscreen mode

Each .md file has this simple structure:

---
title: 'Article 1'
description: 'Article 1 description'
date: '2022-01-01'
---

Article 5 Content
Enter fullscreen mode Exit fullscreen mode

The source code for this demo is available at GitHub and in this StackBlitz playground:

Add Server Route

We will be utilizing the server routes available within Nuxt, and to do so, we'll need to create the server/ directory within our app root directly.

Once this is done, we create a routes/ directory inside this and add a rss.xml.ts file. It will translate to /rss.xml:

export default defineEventHandler(async (event) => {
  const feedString = ''
  event.res.setHeader('content-type', 'text/xml')
  event.res.end(feedString)
})
Enter fullscreen mode Exit fullscreen mode

The next step is to query our blog posts:

import { serverQueryContent } from '#content/server'

export default defineEventHandler(async (event) => {
  const docs = await serverQueryContent(event).sort({ date: -1 }).where({ _partial: false }).find()
  const blogPosts = docs.filter((doc) => doc?._path?.includes('/blog'))

  const feedString = ''
  event.res.setHeader('content-type', 'text/xml')
  event.res.end(feedString)
})
Enter fullscreen mode Exit fullscreen mode

Now let's add the rss library to generate the RSS XML string based on our content:

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

const feed = new RSS({
  title: 'Michael Hoffmann',
  site_url: 'https://mokkapps.de',
  feed_url: `https://mokkapps.de/rss.xml`,
})

const docs = await serverQueryContent(event).sort({ date: -1 }).where({ _partial: false }).find()
const blogPosts = docs.filter((doc) => doc?._path?.includes('/blog'))

for (const doc of blogPosts) {
  feed.item({
    title: doc.title ?? '-',
    url: `https://mokkapps.de${doc._path}`,
    date: doc.date,
    description: doc.description,
  })
}

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

When using nuxt generate, you may want to pre-render the feed since the server route won't be able to run on a static hosting.

We can do this by using the nitro.prerender option in nuxt.config:

import { defineNuxtConfig } from 'nuxt'

// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
  modules: ['@nuxt/content'],
  nitro: {
    prerender: {
      routes: ['/rss.xml'],
    },
  },
  content: {
    // https://content.nuxtjs.org/api/configuration
  },
})
Enter fullscreen mode Exit fullscreen mode

If we now navigate to /rss.xml, we get our generated RSS feed:

<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[ Michael Hoffmann ]]>
    </title>
    <description>
      <![CDATA[ Michael Hoffmann ]]>
    </description>
    <link>https://mokkapps.de</link>
    <generator>RSS for Node</generator>
    <lastBuildDate>Sun, 14 Aug 2022 18:14:16 GMT</lastBuildDate>
    <atom:link href="https://mokkapps.de/rss.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>
        <![CDATA[ Article 5 ]]>
      </title>
      <description>
        <![CDATA[ Article 5 description ]]>
      </description>
      <link>https://mokkapps.de/blog/article-5</link>
      <guid isPermaLink="true">https://mokkapps.de/blog/article-5</guid>
      <pubDate>Thu, 05 May 2022 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>
        <![CDATA[ Article 4 ]]>
      </title>
      <description>
        <![CDATA[ Article 4 description ]]>
      </description>
      <link>https://mokkapps.de/blog/article-4</link>
      <guid isPermaLink="true">https://mokkapps.de/blog/article-4</guid>
      <pubDate>Mon, 04 Apr 2022 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>
        <![CDATA[ Article 3 ]]>
      </title>
      <description>
        <![CDATA[ Article 3 description ]]>
      </description>
      <link>https://mokkapps.de/blog/article-3</link>
      <guid isPermaLink="true">https://mokkapps.de/blog/article-3</guid>
      <pubDate>Thu, 03 Mar 2022 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>
        <![CDATA[ Article 2 ]]>
      </title>
      <description>
        <![CDATA[ Article 2 description ]]>
      </description>
      <link>https://mokkapps.de/blog/article-2</link>
      <guid isPermaLink="true">https://mokkapps.de/blog/article-2</guid>
      <pubDate>Wed, 02 Feb 2022 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>
        <![CDATA[ Article 1 ]]>
      </title>
      <description>
        <![CDATA[ Article 1 description ]]>
      </description>
      <link>https://mokkapps.de/blog/article-1</link>
      <guid isPermaLink="true">https://mokkapps.de/blog/article-1</guid>
      <pubDate>Sat, 01 Jan 2022 00:00:00 GMT</pubDate>
    </item>
  </channel>
</rss>
Enter fullscreen mode Exit fullscreen mode

If you liked this article, follow me on Twitter to get notified about new blog posts and more content from me.

Alternatively (or additionally), you can also subscribe to my newsletter.

Top comments (1)

Collapse
 
kissu profile image
Konstantin BIFERT

A damn nice article + a Github link + Stackblitz demo!
We're spoiled. Thanks Michael! 🤩