Posts tagged with RSS XML Feed

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. 🌱