One-off tip

Support checkout

  1. 1 Choose amount
  2. 2 Payment
  3. 3 Thank you

Choose amount

Pick the level of support that feels right. You can keep it simple or enter a custom amount, then continue to secure payment.

Choose a one-off amount
Adding an RSS feed to an Astro blog
Cover image: scrabble tiles spelling news update on a wooden table
astro engineering web development

Adding an RSS feed to an Astro blog

Content collections, draft filtering, and what an RSS reader actually expects

Published
28 April 2026
Read time
4 min read
SeriesPart of How this blog was built — documenting every decision that shaped this site.

RSS is the oldest and most reliable way to follow a blog. No algorithm, no platform dependency, no notification settings. A reader checks the feed URL, sees new items, shows them. That simplicity is exactly why it’s worth supporting.

Adding an RSS feed to an Astro site is straightforward with the @astrojs/rss package. There are a few things to get right: draft filtering, absolute URLs, and a self-referencing link that validators expect.

Installing the package

Astro doesn’t ship with RSS support out of the box, but the official integration adds everything needed:

Terminal window
pnpm add @astrojs/rss

The feed endpoint

The feed lives at src/pages/rss.xml.js. Astro treats any .js file in src/pages/ as a route, and a named GET export marks it as an endpoint that generates output at build time.

A few parts of this are easy to get wrong.

Draft filtering

The draft flag alone isn’t enough. A post with draft: false and a future pubDate is scheduled, not live. Filtering on !post.data.draft would leak it into the feed before it’s published.

This blog distinguishes three publication states:

StateCondition
draftdraft: true
scheduleddraft: false, future pubDate
publisheddraft: false, past or current pubDate

The isPubliclyPublished utility returns true only for the published state:

src/utils/drafts.ts
export function isPubliclyPublished(post: {
data: { draft: boolean; pubDate: Date };
}): boolean {
return !post.data.draft && post.data.pubDate <= new Date();
}

If you haven’t extracted this into a utility, the inline equivalent is:

const now = new Date();
const posts = (await getCollection("posts"))
.filter((post) => !post.data.draft && post.data.pubDate <= now)
.sort(/* ... */);

Do not use import.meta.env.DEV to conditionally include drafts. The RSS feed should never expose unpublished content, regardless of the build environment.

Sorting

Posts are sorted by pubDate descending so the most recent item appears first. Most RSS readers display items in the order they appear in the feed XML, so the sort order matters.

.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf())

Post links

The link value uses post.id, which in Astro’s Content Layer API is the folder name of each post. For a post at collections/posts/rss-feed-astro/index.md, post.id is rss-feed-astro. Prefixing it with /blog/ produces the correct page URL, with no slug manipulation needed.

link: `/blog/${post.id}/`,

Absolute URLs

The site property on the rss() call is context.site, the value set in astro.config.mjs:

astro.config.mjs
export default defineConfig({
site: "https://sourcier.uk",
// ...
});

The @astrojs/rss package uses this to resolve relative link values into absolute URLs. Without site configured, feed items would have relative URLs that most RSS readers can’t navigate.

RSS validators and some readers expect an <atom:link rel="self"> element in the channel, pointing back to the feed’s own URL. The atom namespace must also be declared on the root <rss> element, which the @astrojs/rss package handles via the xmlns option:

xmlns: {
atom: "http://www.w3.org/2005/Atom",
},
customData: [
`<language>en-gb</language>`,
`<atom:link href="${context.site}rss.xml" rel="self" type="application/rss+xml"/>`,
].join(""),

Without this, the W3C validator flags a “missing atom:link” warning and some readers cannot determine the canonical feed URL.

Validating the feed

Before deploying, it’s worth running the built feed through the W3C Feed Validation Service or RSS Board’s validator. Common mistakes like missing pubDate, non-absolute link values, and invalid XML characters in post content all surface here before they cause problems in readers.

Build the site locally and check dist/rss.xml:

Terminal window
pnpm build && open dist/rss.xml

The raw XML should be readable in the browser. If the browser shows a parse error, something in the feed is malformed.

RSS readers look for a <link rel="alternate"> tag in the page <head> to discover the feed URL automatically. Add it to BaseLayout.astro:

src/layouts/BaseLayout.astro
<link
rel="alternate"
type="application/rss+xml"
title="Sourcier RSS Feed"
href="/rss.xml"
/>

With this in place, browsers and readers that support RSS autodiscovery will surface the feed when a user visits any page on the site.

The complete feed endpoint and autodiscovery link are in the sourcier.uk repository.

Full code listing

Working on something similar?

Need help raising the bar?

I help teams improve engineering practice through hands-on delivery, pragmatic reviews, and mentoring. If you want a second pair of eyes or practical support, let's talk.

  • Engineering practice review
  • Hands-on delivery
  • Team mentoring
Get guidance

If this has been useful, you can back the writing with a one-off tip through a secure Stripe checkout.

Comments

Loading comments…

Leave a comment

Free · Practical · One email per post

Get practical engineering notes

One short email when a new article goes live. Useful if you are breaking into tech, growing as an engineer, or improving engineering practice on your team.