# JSON Feed Alongside RSS: Shipping /feed.json as Well as /feed.xml

RSS is the lingua franca of syndication. JSON Feed is the modern sibling that newer aggregators prefer. Here&#39;s the spec, the differences, and why I ship both from the same source.

Author: J.A. Watte
Published: April 18, 2026
Source: https://jwatte.com/blog/blog-json-feed-modern-syndication/

---

I was setting up a new reader app, one of the AI-aware ones that summarizes feeds and lets you query across them, and noticed it pulled my JSON Feed an order of magnitude faster than my RSS. Not because JSON is inherently faster over the wire (it's not, the payloads were similar sizes), but because the reader was built in a JavaScript stack and consuming JSON was a single `fetch` and `JSON.parse`, while RSS required a dedicated XML parser.

That reader is not unusual. The newer aggregators, AI feed-summarizers, and reader apps are JS-native. Many of them accept JSON Feed as a first-class input and treat RSS as legacy. Shipping both is how you cover the full audience without choosing sides.

## What JSON Feed Is

JSON Feed is a syndication format defined at jsonfeed.org in 2017 by Brent Simmons and Manton Reece. The spec is intentionally short and the format is intentionally boring: it is what you would design if you were translating Atom or RSS 2.0 into idiomatic JSON and trying hard not to invent new concepts along the way.

A minimal feed looks like this:

```json
{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "My Blog",
  "home_page_url": "https://example.com/",
  "feed_url": "https://example.com/feed.json",
  "description": "Notes on shipping static sites.",
  "authors": [
    { "name": "Jane Smith", "url": "https://example.com/about" }
  ],
  "language": "en-US",
  "items": [
    {
      "id": "https://example.com/posts/first-post",
      "url": "https://example.com/posts/first-post",
      "title": "First Post",
      "content_html": "<p>Hello, world.</p>",
      "date_published": "2026-04-18T10:00:00-06:00",
      "date_modified": "2026-04-18T10:00:00-06:00",
      "tags": ["intro", "meta"]
    }
  ]
}
```

Every field maps almost one-to-one to an RSS or Atom equivalent. The `version` field is URL-based on purpose: fetching it returns the spec.

## Why Newer Aggregators Prefer It

Three reasons, in order of importance.

First, **parsing**. Every modern programming language has JSON in the standard library. RSS and Atom both require an XML parser, which is either a heavy dependency or a fragile hand-rolled mess. A JS-based reader app consuming JSON Feed is 5 lines of code. Consuming RSS is a library import and a defensive wrapper around the library's handling of malformed feeds.

Second, **extensibility**. JSON Feed has a designed extension mechanism: any key starting with an underscore is reserved for custom extensions. If a reader wants to add a namespace, it does not need a separate XML namespace declaration and a new parser path. Just add `_reader_name: { ... }` and move on. RSS has extensions too, but they are XML namespaces and they are a pain to parse.

Third, **AI aggregators care about structure**. When a feed-consuming AI summarizer parses your posts, the HTML inside `content_html` is what it extracts for summarization. JSON's delimiter structure is unambiguous, there is no CDATA ambiguity, no escape-vs-entity question, no "did the feed publisher forget to wrap in CDATA" parsing failure. The AI gets clean content out and can summarize accurately.

## Why Keep RSS Too

RSS is not going anywhere. Feedly, NetNewsWire, Inoreader, podcast apps, email-to-RSS bridges, mastodon-style feed-import tools, all of these speak RSS or Atom as their primary. Dropping RSS because you shipped JSON Feed would cut off a huge chunk of your actual readers.

The right answer is both. One post source, two output formats. Neither is hard to generate.

## Generating Both from the Same Source in Eleventy

The pattern I use: one data structure, two templates. In Eleventy, a `posts` collection is built from your Markdown files. The RSS template emits XML, the JSON Feed template emits JSON, both iterate the same collection.

The RSS template:

```
---
permalink: /feed.xml
eleventyExcludeFromCollections: true
---
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>{{ site.title }}</title>
    <link>{{ site.url }}/</link>
    <atom:link href="{{ site.url }}/feed.xml" rel="self" type="application/rss+xml" />
    <description>{{ site.description }}</description>
    <language>en-us</language>
    {% for post in collections.posts | reverse %}
    <item>
      <title>{{ post.data.title }}</title>
      <link>{{ site.url }}{{ post.url }}</link>
      <guid isPermaLink="true">{{ site.url }}{{ post.url }}</guid>
      <pubDate>{{ post.date | rssDate }}</pubDate>
      <description><![CDATA[{{ post.templateContent | safe }}]]></description>
    </item>
    {% endfor %}
  </channel>
</rss>
```

The JSON Feed template, iterating the same collection:

```
---
permalink: /feed.json
eleventyExcludeFromCollections: true
---
{
  "version": "https://jsonfeed.org/version/1.1",
  "title": {{ site.title | dump }},
  "home_page_url": {{ site.url | dump }},
  "feed_url": "{{ site.url }}/feed.json",
  "description": {{ site.description | dump }},
  "language": "en-US",
  "items": [
    {% for post in collections.posts | reverse %}
    {
      "id": "{{ site.url }}{{ post.url }}",
      "url": "{{ site.url }}{{ post.url }}",
      "title": {{ post.data.title | dump }},
      "content_html": {{ post.templateContent | dump }},
      "date_published": "{{ post.date | isoDate }}"{% if post.data.updated %},
      "date_modified": "{{ post.data.updated | isoDate }}"{% endif %}{% if post.data.tags %},
      "tags": {{ post.data.tags | dump }}{% endif %}
    }{% if not loop.last %},{% endif %}
    {% endfor %}
  ]
}
```

The `| dump` Nunjucks filter is what turns strings into JSON-safe quoted values. It handles the escape cases RSS makes you think about.

## Discovery: The `<link>` Tags

Both feeds should be discoverable from the site's `<head>`:

```html
<link rel="alternate" type="application/rss+xml" title="RSS Feed" href="/feed.xml">
<link rel="alternate" type="application/feed+json" title="JSON Feed" href="/feed.json">
```

The `type` attribute is different for each, and the `application/feed+json` MIME type is the one the JSON Feed spec specifies. Some older tools may accept `application/json`, but the spec-compliant type is the one to use.

## Content-Type Headers

For the served files, the Netlify config:

```toml
[[headers]]
  for = "/feed.xml"
  [headers.values]
    Content-Type = "application/rss+xml; charset=utf-8"
    Cache-Control = "public, max-age=1800"

[[headers]]
  for = "/feed.json"
  [headers.values]
    Content-Type = "application/feed+json; charset=utf-8"
    Cache-Control = "public, max-age=1800"
```

30-minute cache is about right for a blog that publishes daily or less. Busier sites may want 5-10 minute caches.

## The Spec Features Worth Using

A few JSON Feed fields that do not have clean RSS equivalents and are worth adding:

- **`banner_image`** per-item: the hero image for the post. JSON Feed makes this a first-class field. RSS requires awkward enclosure or media namespace tricks.
- **`summary`**: a short description separate from the full content. Useful for readers that show a preview list.
- **`external_url`**: if an item is a link-blog post pointing to somewhere else, this field holds the external URL while `url` stays on your site.
- **`_*` custom extensions**: for site-specific metadata a specific reader cares about.

## What About Atom?

Atom (application/atom+xml) is the pedantic, well-specified sibling of RSS. Some reader apps prefer it. If you want to cover every base, emit Atom as a third feed at `/feed.atom`. For most sites, RSS and JSON Feed are enough, the Atom audience is almost fully covered by RSS support.

## Where the Analyzer Checks

The audit at `/tools/mega-analyzer/` detects both feeds from `<link rel="alternate">` tags and validates the served content. JSON Feed is parsed against the v1.1 spec. RSS is parsed against the 2.0 schema. Either one missing is flagged; both missing is a higher-severity flag.

## The Short Version

- JSON Feed at `/feed.json` is the modern sibling of RSS at `/feed.xml`.
- Newer aggregators, AI feed-summarizers, and JS-native readers prefer JSON Feed.
- RSS still has the largest installed base. Keep it.
- One source, two templates. Eleventy (or any static generator) can emit both from the same post collection.
- Link both feeds from `<head>` with `rel="alternate"`, serve them with correct Content-Type headers.
- `application/feed+json` is the correct MIME for JSON Feed.


---

Canonical HTML: https://jwatte.com/blog/blog-json-feed-modern-syndication/
RSS: https://jwatte.com/feed.xml
JSON Feed: https://jwatte.com/feed.json
Hero image: https://jwatte.com/images/blog-json-feed-modern-syndication.webp
