Adding RSS Feeds to Your Astro Blog
RSS feeds let readers subscribe to your blog and get notified of new posts. They’re essential for content sites—and Astro makes them trivial to add.
Why RSS Still Matters
- Newsletter tools — Many use RSS as the source for email digests
- Aggregators — Feedly, Inoreader, and others rely on RSS
- Podcast apps — RSS powers podcast distribution
- Developer audience — Many developers prefer RSS over social algorithms
Installing the RSS Package
npm install @astrojs/rss
Creating the Feed
Add a new page at src/pages/rss.xml.js (or .ts):
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
export async function GET(context) {
const posts = (await getCollection('blog'))
.filter((p) => !p.data.draft)
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
return rss({
title: 'My Blog',
description: 'Latest posts from my blog',
site: context.site,
items: posts.map((post) => ({
title: post.data.title,
description: post.data.description ?? post.data.title,
pubDate: post.data.pubDate,
link: `/blog/${post.slug}/`,
})),
customData: `<language>en-us</language>`,
});
}
Visit /rss.xml and you’ll have a valid RSS 2.0 feed.
JSON Feed
Some readers prefer JSON Feed. Create src/pages/feed.json.js:
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
export async function GET(context) {
const posts = (await getCollection('blog'))
.filter((p) => !p.data.draft)
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
return rss({
title: 'My Blog',
description: 'Latest posts',
site: context.site,
items: posts.map((post) => ({
title: post.data.title,
description: post.data.description ?? post.data.title,
pubDate: post.data.pubDate,
link: `/blog/${post.slug}/`,
})),
stylesheet: false,
format: 'json',
});
}
Linking the Feed
Add auto-discovery in your layout’s <head>:
<link
rel="alternate"
type="application/rss+xml"
title="My Blog RSS"
href="/rss.xml"
/>
For JSON Feed:
<link
rel="alternate"
type="application/json"
title="My Blog JSON Feed"
href="/feed.json"
/>
Including Full Content
By default, items use description (excerpt). For full post body:
import { render } from './render.mjs'; // or use a markdown renderer
items: await Promise.all(
posts.map(async (post) => ({
title: post.data.title,
pubDate: post.data.pubDate,
link: `/blog/${post.slug}/`,
content: (await render(post)).html,
}))
),
Full-content feeds are larger but some readers prefer them for offline reading.
Limiting Items
Most feeds include the last 10–20 posts. Slice the array:
const recent = posts.slice(0, 20);
Caching
Static builds generate the feed at build time. No runtime cost. For SSG, the feed is always fresh when you deploy.
RSS is one of those features that takes 10 minutes to add and pays off for years. Your readers will thank you.