Using built in @astrojs/rss for generation seems simple but there are a few problems & a lot of missing features.

Problems #

I wanted to generate a feed using an excerpt of the blog post content.

There are two problems with this:

RSS 🚀 Astro Documentation

I personally don't have any mdx content in posts so using post.body with markdown-it should be ok.

There are a large number of RSS fields completely missing in @astrojs/rss.
Example missing fields: author, copyright, image, id ...

astro/astro-rss/src/schema.ts

import { z } from 'astro/zod';
export const rssSchema = z.object({
  title: z.string(),
  pubDate: z.union([z.string(), z.number(), z.date()]).transform((value) => new Date(value)),
  description: z.string().optional(),
  customData: z.string().optional(),
  draft: z.boolean().optional(),
});

withastro/astro/packages/astro-rss/src/schema.ts on Github

The output does not use CDATA sections for HTML.

CDATA sections may occur anywhere character data may occur; they are used to escape blocks of text containing characters which would otherwise be recognized as markup.

W3C Extensible Markup Language (XML) 1.0 (Fifth Edition)

Adding Content #

It is possible to input the content of posts (mdx not supported) using markdown-it & sanitize-html.

rss.xml.js

import { getCollection } from 'astro:content'
import { siteConfig } from '~/config'
import createSlug from '~/lib/createSlug'
import slugDate from '~/lib/slugDate'
import sanitizeHtml from 'sanitize-html'
import MarkdownIt from 'markdown-it'
const markdown = MarkdownIt({
  html: true,
  breaks: true,
  linkify: true,
})
content: sanitizeHtml(markdown.render(post.body)),

Adding Excerpt #

I wanted to add an excerpt of the content into the description field of the RSS entries. (most tech blogs I subscribe to do this)

Implementing excerpt can be done in different ways, a lot of implementations just use x characters.
I decided to do it using words (items seperated by spaces)

       title: post.data.title,
       description: sanitizeHtml(
         markdown.render(post.body).split(' ').slice(0, 50).join(' ')
       ),

Kind of simple, take post.body & split it using spaces as seperator, then use slice to take the first 50 items, then join it all back again.

There may be open <p> tags within the excerpt but any open tags will get closed with sanitize-html making the code safe.

Fixing Relative Paths #

Using markdown-it will output relative links & images.
RSS feeds shouldn't have items with relative paths.

To fix this use replace() on href & src objects starting with /.

      description: sanitizeHtml(
        markdown
          .render(post.body)
          .replace('src="/', `src="${siteConfig.url}/`)
          .replace('href="/', `href="${siteConfig.url}/`)
          .split(' ')
          .slice(0, 50)
          .join(' ')
      ),

Notes #

I am looking into generating feeds using an external script (like you would in nextjs)

A few advantages to this over astrojs/rss:


Update February 22, 2023

I am now using a script to generate atom feeds & do not recommend using @astrojs/rss

📝 Atom Feed Generation Script