In my previous article about generating OpenGraph images, I mentioned that Astro isn’t just for generating HTML pages, but also XML. Promise kept: today, we are tackling the monument of the independent web, the RSS feed.

For years, RSS was thought to be dead, killed by social media algorithms that wanted to keep us captive on their platforms. But algorithm fatigue is real. More and more developers (myself included) are returning to aggregators (like Feedly or NetNewsWire) to regain control of their tech watch.

If you have a tech blog, offering an RSS feed is no longer optional; it’s a mark of respect for your readers. And with Astro, it literally takes 5 minutes.

1. The Official Tool

The Astro team had the great idea to create a perfect official integration for this. Start by installing it:

npm install @astrojs/rss

Note: Make sure you have properly defined the site property in your astro.config.mjs file (e.g., site: 'https://vbesse.com'), because the generator will need it to create absolute links.

2. Creating the XML Endpoint

Just like for our dynamic images, we are not going to create a classic page, but an “Endpoint”.

Create a src/pages/en/rss.xml.ts file. Astro will automatically understand that it needs to generate an XML file in your English folder during the build.

import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';

export async function GET(context) {
  // We fetch only our English blog posts
  const blog = await getCollection('blog', ({ id }) => {
    return id.startsWith('en/');
  });

  return rss({
    // The global metadata of your feed
    title: 'Valentin Besse\'s Blog',
    description: 'Welcome to my tech blog. We talk about code, Astro, and legacy.',
    // The context automatically provides your site's URL
    site: context.site,
    // We loop through our posts to create the feed items
    items: blog.map((post) => {
      // Clean the slug to keep the URLs clean
      const cleanSlug = post.slug.replace('en/', '');
      
      return {
        title: post.data.title,
        pubDate: post.data.pubDate,
        description: post.data.description,
        // We generate the absolute link to the article
        link: `/en/blog/${cleanSlug}/`,
      };
    }),
    // Optional: Specify the language for readers
    customData: `<language>en-us</language>`,
  });
}

And… that’s it for the generation! If you start your development server and go to /en/rss.xml, you will see a beautiful page of raw XML code. Your future readers will be able to copy this link into their favorite aggregator.

3. Auto-Discovery (The Step Everyone Forgets)

Generating the feed is good. But browsers and reading apps need to know it exists without the user having to manually search for the link.

This is where our famous architectural component BaseHead.astro (which we talked about a few days ago) shines again.

Add this simple line to your <head> tag:

<link 
  rel="alternate" 
  type="application/rss+xml" 
  title="Valentin Besse's Blog (English)" 
  href={new URL("en/rss.xml", Astro.site)} 
/>

Thanks to this rel="alternate" tag, browser extensions and aggregators will automatically detect the presence of your feed as soon as a visitor lands on your site.

Conclusion

In less time than it takes to mindlessly scroll your LinkedIn feed, we’ve just made our content accessible in an asynchronous, persistent, and decentralized way.

The web belongs to the readers again. Subscribe to my feed (the button is in the footer!) so you don’t miss the next article.