RSS feed with Tanstack Start

By Jeremy Harland
Apr 21, 20262 min read9 views0 comments0 likes

I have been slowly working at this site more since Tanstack Start has been in RC. This is my first time building with Tanstack Router, coming from a background primarily working on Nextjs with react query. Some conventions were slightly different along the way but mostly quite similar concepts.


Tanstack Start Docs

The Tanstack Start documentation are great and comes with an SEO guide that outlines how to create sitemaps.xml and robots.txt files statically and dynamically. I had no need to create a dynamic robots.txt for this site and opted to just use Tanstack's built-in sitemap generator. Reading the guide on the the dynamic robots.txt would have shown exactly what to do for the rss.xml file. The docs also recommend reading the Tanstack Router as they are coupled by design.

Generating a dynamic rss.xml file

Perquisites

This guide assumes you have a way to return all your posts/content and your tanstack start site is not statically deployed.

Server Route

Create a server route with the file src/routes/rss[.]xml.ts, this was not abundantly clear to me but is outlined in the File Name Conventions apart of the router documentation. Download the rss npm package with

pnpm install rss

or with whatever package manager you are using.

Then after configuring your posts you should end up with a file looking like

import { env } from '@/env'
import { createFileRoute } from '@tanstack/react-router'
import { allPosts } from 'content-collections'
import RSS from 'rss'

export const Route = createFileRoute('/rss.xml')({
  server: {
    handlers: {
      GET: async () => {
        const siteUrl = env.PUBLIC_BASE_URL

        const feed = new RSS({
          title: '<Your Title Here>',
          description:
            '<Your description Here>',
          feed_url: `${siteUrl}/rss.xml`,
          site_url: siteUrl,
          language: 'en',
          pubDate: new Date(),
          copyright: `All rights unreserved ${new Date().getFullYear()}`,
        })

        allPosts.map((post) => {
          feed.item({
            title: post.title,
            description: post.description,
            author: post.authors.join(', '),
            url: `${siteUrl}/blog/${post.slug}`,
            date: post.published,
            custom_elements: [
              { 'content:encoded': { _cdata: post.rendered.markup } },
            ],            
            enclosure: post.headerImage
              ? {
                  url: post.headerImage,
                  type: 'image/jpeg',
                }
              : undefined,
          })
        })
        return new Response(feed.xml({ indent: true }), {
          status: 200,
          headers: {
            'Content-Type': 'text/xml',
          },
        })
      },
    },
  },
})

Configuration

  • In your headers you can control how long your want to cache this for or not at all
  • There is more rss options you can configure or less! I like to add the image if it is present as most modern readers will render this in there ui.

Comments

No comments yet. Be the first!