Atom feed for Astro

Vision alchemist crafting strategic innovation & AI adoption. Bridging startups to China’s ecosystem advantage. Building a cyberbrain. Registered pharmacist × Brand strategist × Storyteller
When I moved this site to Astro, I’ve set up the feed using Astro’s RSS plugin, which generates an RSS 2.0 feed. But recently, I stumbled upon Nic Chan’s article where he described using the framework-agnostic Feed plugin to generate Atom 1.0 instead.
I’ve always preferred Atom over RSS so I made the switch immediately.
RSS vs Atom
RSS has wider compatibility and simpler structure compared to Atom. But it’s 2024, and worrying about Atom support feels a bit like fretting over whether your new car has a USB media player.
Atom has several compelling advantages that make it worth considering:
- Better standardisation with clear, formal specifications
- Superior support for rich content
- Improved support for internationalisation
- The smug satisfaction of using something slightly more sophisticated
And the best part? The Feed package supports both RSS and Atom, working seamlessly with Astro right out of the box—like finding out your new electric car still has a spot for your thumb drive music library after all.
Setting up Feed with Astro
Adding the jpmonette/feed
package to your Astro project is straightforward.
1. Install jpmonette/feed
First, install the package using your package manager of choice:
Using yarn: yarn add feed
Using pnpm: pnpm add feed
2. Create endpoint
Create an API endpoint in Astro to generate your feed by creating a file at src/pages/feed.xml.js
:
// src/pages/feed.xml.js
import { Feed } from 'feed';
import { getCollection } from 'astro:content';
export async function GET(context) {
const blog = await getCollection('blog');
const siteUrl = 'https://yoursite.com';
const feed = new Feed({
title: 'Your Site Name',
description: 'Your site description',
id: siteUrl,
link: siteUrl,
language: 'en',
favicon: `${siteUrl}/favicon.ico`,
copyright: `All rights reserved ${new Date().getFullYear()}`,
feedLinks: {
rss: `${siteUrl}/rss.xml`,
},
author: {
name: 'Your Name',
},
});
blog.forEach((post) => {
const url = `${siteUrl}/blog/${post.slug}`;
feed.addItem({
title: post.data.title,
id: url,
link: url,
description: post.data.description,
content: post.body, // Or process this with markdown if needed
author: [
{
name: post.data.author || 'Your Name',
},
],
date: new Date(post.data.publishDate),
});
});
return new Response(feed.rss2(), {
headers: {
'Content-Type': 'application/xml',
},
});
}
3. Add link to <head>
Declare the feed by adding a link to it in your site’s <head>
section:
<link rel="alternate" type="application/atom+xml" title="Your Site Atom Feed" href="/feed.xml">
Parsing Markdown
After following the steps above, I found my feed serving raw Markdown instead of proper HTML.
The Astro RSS documentation recommends using SanitizeHTML and MarkdownIt to parse the content properly.
1. Install the packages
Install the necessary packages:
Using yarn: yarn add sanitize-html markdown-it
Using pnpm: pnpm add sanitize-html markdown-it
2. Modify the endpoint
Modify src/pages/feed.xml.js
to parse the content:
// src/pages/feed.xml.js
import { Feed } from 'feed';
import { getCollection } from 'astro:content';
import sanitizeHtml from 'sanitize-html';
import MarkdownIt from 'markdown-it';
export async function GET(context) {
const siteUrl = 'https://yoursite.com';
// Initialize markdown parser
const md = new MarkdownIt({
html: true, // Enable HTML tags in source
breaks: true, // Convert '\n' in paragraphs into <br>
linkify: true // Autoconvert URL-like text to links
});
// Create feed instance
const feed = new Feed({
title: 'Your Site Name',
description: 'Your site description',
id: siteUrl,
link: siteUrl,
language: 'en',
favicon: `${siteUrl}/favicon.ico`,
copyright: `All rights reserved ${new Date().getFullYear()}`,
feedLinks: {
atom: `${siteUrl}/feed.xml`,
},
author: {
name: 'Your Name',
},
updated: new Date(),
});
const blog = await getCollection('blog');
// Add each post to the feed
blog.forEach((post) => {
const url = `${siteUrl}/blog/${post.slug}`;
// Parse markdown to HTML
const htmlContent = md.render(post.body);
// Sanitize HTML to remove potentially unsafe tags
const sanitizedContent = sanitizeHtml(htmlContent, {
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img', 'h1', 'h2', 'h3']),
allowedAttributes: {
...sanitizeHtml.defaults.allowedAttributes,
img: ['src', 'alt', 'title'],
a: ['href', 'name', 'target', 'rel']
},
// Transform relative URLs to absolute URLs
transformTags: {
'a': (tagName, attribs) => {
if (attribs.href && attribs.href.startsWith('/')) {
return {
tagName: 'a',
attribs: {
...attribs,
href: `${siteUrl}${attribs.href}`,
target: '_blank',
rel: 'noopener'
}
};
}
return { tagName, attribs };
},
'img': (tagName, attribs) => {
if (attribs.src && attribs.src.startsWith('/')) {
return {
tagName: 'img',
attribs: {
...attribs,
src: `${siteUrl}${attribs.src}`
}
};
}
return { tagName, attribs };
}
}
});
feed.addItem({
title: post.data.title,
id: url,
link: url,
description: post.data.description || '',
content: sanitizedContent,
author: [
{
name: post.data.author || 'Your Name',
},
],
date: new Date(post.data.publishDate),
updated: post.data.modified ? new Date(post.data.modified) : new Date(post.data.publishDate)
});
});
return new Response(feed.atom1(), {
headers: { 'Content-Type': 'application/atom+xml' }
});
}
Multiple feeds
Can’t decide between RSS and Atom? Why not both? Creating endpoints for each format allows readers to subscribe using their preferred method. Here’s how to set it up:
Create two endpoint files and modify headers returned.
For RSS 2.0 at /rss.xml
:
// src/pages/rss.xml.js
return new Response(feed.rss2(), {
headers: { 'Content-Type': 'application/xml' }
});
For Atom at /atom.xml
:
src/pages/atom.xml.js
return new Response(feed.atom1(), {
headers: { 'Content-Type': 'application/atom+xml' }
});
Wrapping up
Setting up Atom feeds with Astro and the Feed package greatly improves how my content is delivered to subscribers. The standardised format ensures better compatibility with modern feed readers while maintaining all the rich content from my posts.
Whether you choose RSS, Atom, or both, making your content accessible through feeds gives readers more control over how they consume your work—something that aligns perfectly with the principles of owning your data.