JSONFeed in Astro with @astrojs/rss
I wanted this website to publish both an RSS feed and a JSONFeed. The catch: I didn’t want to maintain two separate feed pipelines.
Astro already has a handy @astrojs/rss
package that outputs RSS feeds. So I thought—why not reuse the same data shape to generate JSONFeed as well?
Turns out, it’s pretty straightforward.
The Code
Here’s how I reused the @astrojs/rss
shape to build a JSONFeed endpoint in Astro.
Formatting the Feed
I built a transformer function to take the RSSOptions
and convert them to be a valid JSONFeed 1.1 top-level.
import type { APIContext } from "astro";
import { getCollection } from "astro:content";
import type { RSSFeedItem, RSSOptions } from "@astrojs/rss";
export const formatJsonFeed = (rssOptions: RSSOptions) => {
const { site, title, description } = rssOptions;
const homePageUrl = site instanceof URL ? site.href : site;
return {
version: "https://jsonfeed.org/version/1.1",
title: title,
home_page_url: homePageUrl,
feed_url: new URL("/feed.json", homePageUrl).href,
description: description,
items: [],
};
};
Formatting Feed Items
Next was transforming RSS items to be valid JSONFeed 1.1 items.
export const formatJsonFeedItem = (rssFeedItem: RSSFeedItem) => {
const { link, content, title, pubDate, description } = rssFeedItem;
if (!link) {
throw new Error("RSS feed item must have a link.");
}
return {
id: link,
url: link,
title: title,
summary: description,
date_published: pubDate?.toISOString(),
content_html: content || "",
};
};
Putting It Together
Next, you’ll need to create a Static File Endpoint to serve the JSONFeed. I put mine at /src/pages/feed.json.ts
, which servers it at https://example.com/feed.json
.
export async function GET(context: APIContext): Promise<Response> {
const posts = await getCollection("posts");
const jsonFeedItems = posts.map((post) =>
formatJsonFeedItem({
link: url.href,
title: post.data.name,
pubDate: post.data.publishedAt,
}),
);
const jsonFeed = formatJsonFeed({
title: "Website Feed",
description: "This is a JSONFeed!!!",
site: context.site,
items: jsonFeedItems,
});
return new Response(JSON.stringify(jsonFeed, null, 2), {
headers: { "Content-Type": "application/feed+json" },
});
}
Auto-Discovery of Your New JSONFeed
To make your JSONFeed discoverable by browsers and feed readers, add a <link>
tag in your site’s <head>
. For example:
<link
rel="alternate"
type="application/rss+xml"
title="RSS Feed"
href="/rss.xml"
/>
<link
rel="alternate"
type="application/feed+json"
title="JSONFeed"
href="/feed.json"
/>
This way, clients that support feed auto-discovery will automatically pick up both formats.
Wrap Up
Now my site has two feeds: RSS for the traditionalists and JSONFeed for the modernists. If you’re already using @astrojs/rss
, this pattern makes JSONFeed nearly free. Give it a try—and let me know if you’d like me to extend this into Atom or other formats. 🌱