Skip to content

feat: provide rss, atom and json feeds for the blog#2562

Open
Kiwow wants to merge 31 commits into
npmx-dev:mainfrom
Kiwow:rss
Open

feat: provide rss, atom and json feeds for the blog#2562
Kiwow wants to merge 31 commits into
npmx-dev:mainfrom
Kiwow:rss

Conversation

@Kiwow

@Kiwow Kiwow commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

I'm opening this as a draft so that progress on it is transparent. Feedback is appreciated even before it's ready!

🔗 Linked issue

Resolves #2489

The plan

  • Generate RSS, Atom and JSON feeds from blog posts using the feed package
    • Generate these at build time
    • Use the data that modules/blog.ts is creating in .nuxt/blog/posts.ts, possibly in the same module setup
    • Use prerendered routes, similarly to how opensearch.xml gets served (thanks to @t128n for the pointer on this)
  • Serve the feeds at "/rss.xml", "/atom.xml" and "/feed.json" respectively
    • Serve with types "application/rss+xml", "application/atom+xml" and "application/feed+json"
  • Provide <link rel="alternate"> elements for readers to find the feeds
  • Verify that this works (with an e2e test)
  • Verify that this works (manually, in a prod build & in dev)

Checklist

Before merging, I need to make sure that:

Issues / obstacles:

The feed package currently doesn't support JSON Feed 1.1, which added support for multiple authors. This means it silently throws away all but the first author we specify in an the author array. Manually parsing the object after it gets generated and adding in the other authors is an option, but then we'd be lying about the version that we're serving. Alternatively, it shouldn't be that hard to write the JSON Feed serializer ourselves (the feed implementation is a bit over 100 lines of code)

RSS (the spec) seemingly doesn't support multiple authors at all, or just in convention, where you put everyone into an <author> tag. I'll have to try and test some readers / find how feeds handle multiple authors to figure out a good solution. Either way, the feed package includes multiple <author> tags, which seems to be valid in Atom but not in RSS.

Also see my update comment on this

@vercel

vercel Bot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs.npmx.dev Ready Ready Preview, Comment Jul 9, 2026 5:16pm
npmx.dev Ready Ready Preview, Comment Jul 9, 2026 5:16pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
npmx-lunaria Ignored Ignored Jul 9, 2026 5:16pm

Request Review

@coderabbitai

coderabbitai Bot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds syndicated feeds (RSS, Atom, JSON) with a feed-generation utility and server routes, injects alternate feed links into the blog page, updates Nuxt/Nitro config and esbuild, adds the feed package, and adds Playwright e2e tests verifying discoverability, content type and CORS headers.

Changes

Blog feeds feature

Layer / File(s) Summary
Data / Helpers
server/utils/feeds.ts
New exported getFeed() with makeUrlAbsolute() that constructs a fresh Feed per call using hardcoded site metadata and non-draft posts, mapping authors, absolute URLs, dates and optional images into feed items (no module-level caching).
Framework config
nuxt.config.ts
Added routeRules to prerender /rss.xml, /atom.xml, /feed.json and attach Access-Control-Allow-Origin: * and feed-specific Content-Type headers; extended nitro.esbuild.options.exclude to allow .cache/nuxt/.nuxt/blog/posts.ts to be processed.
End-to-end tests
test/e2e/feeds.spec.ts
New Playwright tests that read alternate feed links from /blog, fetch each feed path, and assert feed Content-Type, Access-Control-Allow-Origin: *, and that link hrefs are absolute https://npmx.dev URLs.
Dependencies
package.json
Added feed v5.2.0 to devDependencies.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant BlogPage as Blog Page
    participant FeedRoute as Feed Route (/rss.xml, /atom.xml, /feed.json)
    participant FeedUtil as getFeed()
    participant BlogPosts as Blog Posts Data

    Browser->>BlogPage: Request /blog
    BlogPage-->>Browser: Render page with alternate feed links

    Browser->>FeedRoute: Request feed endpoint (via link)
    FeedRoute->>FeedUtil: Call getFeed()
    FeedUtil->>BlogPosts: Load posts (exclude drafts)
    BlogPosts-->>FeedUtil: Return posts
    FeedUtil->>FeedUtil: Build new Feed object (items, authors, links, dates)
    FeedUtil-->>FeedRoute: Return Feed
    FeedRoute->>FeedRoute: Serialize (.rss2/.atom1/.json1)
    FeedRoute-->>Browser: Respond with feed + Content-Type + CORS
Loading

Suggested reviewers

  • graphieros
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the primary change: adding RSS, Atom, and JSON feed functionality for the blog.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, documenting implementation details, design decisions, known issues, and validation efforts.
Linked Issues check ✅ Passed The PR successfully addresses issue #2489 by implementing RSS, Atom, and JSON feed generation and serving at canonical endpoints with proper MIME types, auto-discovery links, and CORS support.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #2489 objectives: feed generation, serving, auto-discovery, and testing. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Apr 17, 2026

Copy link
Copy Markdown

📊 Dependency Size Changes

Warning

This PR adds 447.8 kB of new dependencies, which exceeds the threshold of 200 kB.

📦 Package 📏 Size
xml-js@1.6.11 420.6 kB
feed@6.0.0 27.3 kB
vue-component-type-helpers@3.3.6 → vue-component-type-helpers@3.3.7 0 B

Total size change: 447.8 kB

@codecov

codecov Bot commented Apr 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 9 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
server/utils/feeds.ts 0.00% 6 Missing ⚠️
server/routes/atom.xml.get.ts 0.00% 1 Missing ⚠️
server/routes/feed.json.get.ts 0.00% 1 Missing ⚠️
server/routes/rss.xml.get.ts 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Comment thread modules/blog.ts Outdated
const atomPath = 'atom.xml'
const jsonFeedPath = 'feed.json'

await Promise.all([

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instead of saving these files to disk, creating pre-rendered server-side routes could be a cleaner approach 🤔. Similar to server/routes/opensearch.xml.get.ts with https://nuxt.com/docs/4.x/getting-started/prerendering#selective-pre-rendering

@Kiwow Kiwow Apr 18, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like exactly what I was looking for and didn't find. Thanks!

I'll add a mention of this to the PR description and look into it when I have time to work on this again.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
test/e2e/feeds.spec.ts (1)

20-49: Test is mostly solid; a couple of small robustness nits.

  • expect(href.slice(0, 16)).toBe('https://npmx.dev') is correct arithmetic ('https://npmx.dev'.length === 16) but brittle — any host-name change silently breaks both the prefix check and the derived url = href.slice(16). Prefer matching on the parsed URL so intent is obvious and the "derive path from href" step can't go wrong:

    -        // href is an absolute link
    -        expect(href.slice(0, 16)).toBe('https://npmx.dev')
    -
    -        const { contentType, corsHeader } = await page.evaluate(async href => {
    -          // Fetch the same path as in the alternate link
    -          const url = href.slice(16)
    -          const response = await fetch(url)
    +        // href must be an absolute URL on the production origin
    +        const parsed = new URL(href)
    +        expect(parsed.origin).toBe('https://npmx.dev')
    +
    +        const { contentType, corsHeader } = await page.evaluate(async path => {
    +          const response = await fetch(path)
               return {
                 contentType: response.headers.get('Content-Type'),
                 corsHeader: response.headers.get('Access-Control-Allow-Origin'),
               }
    -        }, href)
    +        }, parsed.pathname)
  • await expect(locator).toHaveAttribute('href') without a second argument checks existence (fine), and the subsequent getAttribute('href') + expect(href).not.toBeNull() + typeof href !== 'string' guard is a bit redundant. You can drop the first assertion and keep the null/string guard, since expect(href).not.toBeNull() already fails the test on absence.

  • Minor: response.headers.get('Content-Type') returns the full header value, which may include charset parameters (application/rss+xml; charset=utf-8) depending on the server. Right now toBe(feed.contentType) would break if Nitro ever adds a charset. Consider expect(contentType).toMatch(new RegExp('^' + feed.contentType)) to be forward-compatible.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/feeds.spec.ts` around lines 20 - 49, The test should parse the
alternate link href with the URL API instead of slicing and remove the redundant
locator existence assert; change the code to getAttribute('href') into href,
guard for null/string, then create const parsed = new URL(href) and assert
parsed.origin === 'https://npmx.dev' (or the intended host) and pass
parsed.pathname+parsed.search into page.evaluate for fetching; finally relax the
content-type check to match the media type prefix (e.g., use a startsWith or
regex like '^' + feed.contentType) when asserting
response.headers.get('Content-Type') so charset parameters won't break the test
— update references: locator, href, page.evaluate, response.headers.get and the
expect(contentType) assertion accordingly.
nuxt.config.ts (1)

195-206: CORS + Content-Type via route rules — looks good.

Matches the PR checklist (enable CORS, serve correct MIME types) and the values are exactly what test/e2e/feeds.spec.ts asserts. One small thought: if you want feeds to be cacheable by intermediaries, consider adding a Cache-Control: public, max-age=… header alongside — otherwise feed aggregators may hit the origin more often than needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nuxt.config.ts` around lines 195 - 206, The route rules for '/rss.xml',
'/atom.xml', and '/feed.json' currently set CORS and Content-Type but lack
caching; update the headers object in nuxt.config.ts for the route keys
'/rss.xml', '/atom.xml', and '/feed.json' to include a Cache-Control header
(e.g. 'Cache-Control': 'public, max-age=3600') so intermediaries can cache
feeds; modify the headers for the RouteRule entries (the objects used for these
paths) accordingly and run/update any tests that assert exact header sets if
needed.
app/pages/blog/index.vue (1)

19-40: Remove hard-coded feed URLs and title; use root-relative hrefs or derive from site config.

The three hard-coded https://npmx.dev/* hrefs and the fixed 'Blog - npmx' title duplicate values already present in nuxt.config.ts (site.url, site.name) and in server/utils/feeds.ts. If the canonical origin ever changes (staging, preview branches, a rename), these links will silently point at the wrong host. Also note these are already absolute on a blog page on npmx.dev — a root-relative href (/rss.xml, etc.) is equally valid for <link rel="alternate"> and would avoid hard-coding the origin on the client.

Consider either:

Option A — root-relative hrefs
-      href: 'https://npmx.dev/rss.xml',
+      href: '/rss.xml',
...
-      href: 'https://npmx.dev/atom.xml',
+      href: '/atom.xml',
...
-      href: 'https://npmx.dev/feed.json',
+      href: '/feed.json',
Option B — derive from site config
const siteConfig = useSiteConfig()
const origin = siteConfig.url // 'https://npmx.dev'
useHead({
  link: [
    { rel: 'alternate', title: `${siteConfig.name} Blog`, type: 'application/rss+xml',  href: `${origin}/rss.xml` },
    { rel: 'alternate', title: `${siteConfig.name} Blog`, type: 'application/atom+xml', href: `${origin}/atom.xml` },
    { rel: 'alternate', title: `${siteConfig.name} Blog`, type: 'application/feed+json', href: `${origin}/feed.json` },
  ],
})

Note: useSiteConfig() is already used elsewhere in the codebase (app/components/OgImage/Splash.takumi.vue), making Option B a viable pattern. Also consider updating the matching hard-coded URLs in server/utils/feeds.ts for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/pages/blog/index.vue` around lines 19 - 40, Replace the hard-coded feed
hrefs and title in the useHead call with either root-relative paths or values
derived from the site's config: stop using 'https://npmx.dev/*' and the fixed
'Blog - npmx' string in the link array inside useHead; instead call
useSiteConfig() (already used elsewhere), read siteConfig.url (if you need
absolute origin) and siteConfig.name (for the title), and set hrefs to
'/rss.xml', '/atom.xml', '/feed.json' or to `${siteConfig.url}/rss.xml` etc.,
and set title to `${siteConfig.name} Blog`; update the same pattern in
server/utils/feeds.ts as well for consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nuxt.config.ts`:
- Around line 243-251: The esbuild exclude regex under the esbuild options
currently targets node_modules/.cache/nuxt/.nuxt/blog/posts.ts which won't match
Nuxt's actual buildDir (e.g. .nuxt/blog/posts.ts); update the exclude pattern in
the esbuild.options.exclude to allow the generated .nuxt/blog/posts.ts (or
otherwise include .nuxt/**/blog/posts.ts) so the `#blog/posts` import isn't
excluded, and add a simple runtime/build-time assertion or log of the resolved
buildDir used by Nuxt (referenced from server/utils/feeds.ts) to surface
mismatches early; also fix the comment path to use forward slashes
(server/utils/feeds.ts) for consistency.

In `@package.json`:
- Line 136: Move the "feed" package from devDependencies to dependencies in
package.json so it is available at runtime; update package.json by removing
"feed": "5.2.0" from devDependencies and adding the same entry under
dependencies. This ensures imports in server/utils/feeds.ts (used by the
/rss.xml, /atom.xml and /feed.json routes with prerender: true) are present in
production builds and prevents runtime errors if those routes are ever rendered
on-demand.

In `@server/utils/feeds.ts`:
- Around line 4-17: Add a short clarifying comment above the module-level
variable "feed" explaining that this cache is build-time only because getFeed()
calls generateFeed() once at module import (used for prerendered /rss.xml,
/atom.xml, /feed.json), and that if prerendering/ISR is removed the cache will
become stale and must be invalidated or regenerated (e.g., reset feed or call
generateFeed on each request); reference the "feed" variable, the getFeed()
function and generateFeed() to make the intended lifetime and required
invalidation explicit for future maintainers.
- Around line 19-54: generateFeed currently passes post.image through unchanged
causing broken thumbnails; update the feed.addItem call in function generateFeed
to normalize post.image the same way author.avatar is normalized (e.g., if
post.image exists, set image to new URL(post.image, siteUrl).toString()), and
replace the hard-coded title/description/id/link values with values pulled from
the shared site config (useSiteConfig() or equivalent) so title/description/site
URL are derived from the single source of truth used by nuxt.config.ts and
app/pages/blog/index.vue.

---

Nitpick comments:
In `@app/pages/blog/index.vue`:
- Around line 19-40: Replace the hard-coded feed hrefs and title in the useHead
call with either root-relative paths or values derived from the site's config:
stop using 'https://npmx.dev/*' and the fixed 'Blog - npmx' string in the link
array inside useHead; instead call useSiteConfig() (already used elsewhere),
read siteConfig.url (if you need absolute origin) and siteConfig.name (for the
title), and set hrefs to '/rss.xml', '/atom.xml', '/feed.json' or to
`${siteConfig.url}/rss.xml` etc., and set title to `${siteConfig.name} Blog`;
update the same pattern in server/utils/feeds.ts as well for consistency.

In `@nuxt.config.ts`:
- Around line 195-206: The route rules for '/rss.xml', '/atom.xml', and
'/feed.json' currently set CORS and Content-Type but lack caching; update the
headers object in nuxt.config.ts for the route keys '/rss.xml', '/atom.xml', and
'/feed.json' to include a Cache-Control header (e.g. 'Cache-Control': 'public,
max-age=3600') so intermediaries can cache feeds; modify the headers for the
RouteRule entries (the objects used for these paths) accordingly and run/update
any tests that assert exact header sets if needed.

In `@test/e2e/feeds.spec.ts`:
- Around line 20-49: The test should parse the alternate link href with the URL
API instead of slicing and remove the redundant locator existence assert; change
the code to getAttribute('href') into href, guard for null/string, then create
const parsed = new URL(href) and assert parsed.origin === 'https://npmx.dev' (or
the intended host) and pass parsed.pathname+parsed.search into page.evaluate for
fetching; finally relax the content-type check to match the media type prefix
(e.g., use a startsWith or regex like '^' + feed.contentType) when asserting
response.headers.get('Content-Type') so charset parameters won't break the test
— update references: locator, href, page.evaluate, response.headers.get and the
expect(contentType) assertion accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dae01d38-ddfa-4c63-be76-09ac26327e96

📥 Commits

Reviewing files that changed from the base of the PR and between 5cfe58f and 453ceb5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • app/pages/blog/index.vue
  • nuxt.config.ts
  • package.json
  • server/routes/atom.xml.get.ts
  • server/routes/feed.json.get.ts
  • server/routes/rss.xml.get.ts
  • server/utils/feeds.ts
  • test/e2e/feeds.spec.ts

Comment thread nuxt.config.ts
Comment thread package.json Outdated
"devalue": "5.6.4",
"eslint-plugin-regexp": "3.1.0",
"fast-check": "4.6.0",
"feed": "5.2.0",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm the feed package is only used behind prerendered routes and nowhere on a runtime path.
rg -nP "from ['\"]feed['\"]|require\(['\"]feed['\"]\)" --type=ts --type=js --type=vue
echo '---'
# Double-check no non-prerendered route imports getFeed
rg -nP -C2 "getFeed\s*\(" --type=ts

Repository: npmx-dev/npmx.dev

Length of output: 992


🏁 Script executed:

# Check where feed is imported in feeds.ts
cat -n server/utils/feeds.ts | head -20

echo '=== Checking nuxt.config.ts for prerender settings ==='
# Search for prerender configurations for the feed routes
rg -nP "atom\.xml|rss\.xml|feed\.json" nuxt.config.ts

echo '=== Current package.json state for feed ==='
# Check where feed is currently placed in package.json
rg -nP -B2 -A2 '"feed":\s*"5\.2\.0"' package.json

echo '=== Searching for any other getFeed or feed imports ==='
# Broader search for any other getFeed invocations
rg -nP "getFeed" --type=ts

Repository: npmx-dev/npmx.dev

Length of output: 1513


🏁 Script executed:

# View the prerender configuration for the feed routes in nuxt.config.ts
sed -n '193,210p' nuxt.config.ts

Repository: npmx-dev/npmx.dev

Length of output: 693


Move feed from devDependencies to dependencies for runtime safety.

feed is imported in server/utils/feeds.ts and used only by three routes (/rss.xml, /atom.xml, /feed.json), all of which have prerender: true in nuxt.config.ts. While this means the dependency is needed only at build time in a fully prerendered deploy, moving it to dependencies is recommended as a safer posture. If any of these routes ever fall back to runtime rendering—such as an ISR fallback, dev preview in production mode, or a prerender failure served on-demand—Nitro's bundled output will reference feed at runtime and it will be missing in a --prod/--production install.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 136, Move the "feed" package from devDependencies to
dependencies in package.json so it is available at runtime; update package.json
by removing "feed": "5.2.0" from devDependencies and adding the same entry under
dependencies. This ensures imports in server/utils/feeds.ts (used by the
/rss.xml, /atom.xml and /feed.json routes with prerender: true) are present in
production builds and prevents runtime errors if those routes are ever rendered
on-demand.

Comment thread server/utils/feeds.ts Outdated
Comment thread server/utils/feeds.ts Outdated
@Kiwow
Kiwow marked this pull request as ready for review May 10, 2026 14:00
@Kiwow

Kiwow commented May 10, 2026

Copy link
Copy Markdown
Contributor Author

When it comes to feed validity, I don't love the situation but I'm happy to defer the decision to the community:

  • The Atom feed is valid per the W3C validator,
  • the RSS feed has problems (mainly related to author fields) and
  • I wasn't able to validate the JSON feed programatically, but aside from the fact that feed doesn't support 1.1 (and with it, multiple authors, as coderabbit also points out), it seems valid

I think the possible solutions here are to just accept what feed supports and benefit when/if they update, or to write a specialized RSS and JSON feed serializer to improve what the package can't give us here.

Personally, I took time off this PR because life happened, but I'd be happy to try and write up a custom implementation if that makes sense to people. If so, I'm also unsure whether that should be part of this PR or a follow-up.

 Conflicts:
	pnpm-lock.yaml

        ^ automerged with `pnpm i`
@ghostdevv

ghostdevv commented May 31, 2026

Copy link
Copy Markdown
Member

hey, thanks for the pr!

I wasn't able to validate the JSON feed programatically, but aside from the fact that feed doesn't support 1.1 (and with it, multiple authors, as coderabbit also #2562 (comment)), it seems valid

Do you know if anyone uses JSON feeds? It's the first I'm hearing of them so another option is to just not include it if we don't know of any users. If people do use it then the feed library appears to be maintained so we can try PRing support for the authors field, and in the meantime use pnpm patch to get support ourselves.

Personally, I took time off this PR because life happened

No worries! Do you have any time at the moment? If not no worries, we can pick up where you left off - it seems as though we're nearly there anyway 👀

 Conflicts:
	package.json
	pnpm-lock.yaml
@socket-security

socket-security Bot commented May 31, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedfeed@​6.0.010010010088100

View full report

@Kiwow

Kiwow commented May 31, 2026

Copy link
Copy Markdown
Contributor Author

Hi there @ghostdevv, thanks for the response

Do you know if anyone uses JSON feeds? It's the first I'm hearing of them so another option is to just not include it if we don't know of any users. If people do use it then the feed library appears to be maintained so we can try PRing support for the authors field, and in the meantime use pnpm patch to get support ourselves.

I know of a couple of blogs that offer them (Mat Marquis' blog is one that comes to mind), but to be frank I've never used a reader that supports them. I will say that implementing support for the 1.1 spec sounds fun, whether it would stay as a patch here or be upstreamed.

No worries! Do you have any time at the moment? If not no worries, we can pick up where you left off - it seems as though we're nearly there anyway 👀

It's a pretty busy time for me for the next two weeks, but I'd be happy to fit in at least a bit of work on this. On the other hand, I understand if you'd rather get RSS support in ASAP, especially if a blog post is coming soon. I do think that the changes in this PR work, even if they might not be the cleanest possible solution.

If you think implementing the features the JSON feed needs in a pnpm patch is the right way to go, I'd be happy to work on that. Otherwise, like I said, in two weeks, I'd love to work on this more. I think I would just need a review for some guidance, e.g. if the esbuild exclude hack is okay to keep or if I should find a better way.

@ghostdevv

Copy link
Copy Markdown
Member

Mat Marquis' blog is one that comes to mind

His isn't valid JSON currently 🤔

If you think implementing the features the JSON feed needs in a pnpm patch is the right way to go, I'd be happy to work on that. Otherwise, like I said, in two weeks, I'd love to work on this more.

Let me see what the other maintainers thing about the feed.json, and then we can either patch it in or discard it based on their thoughts. Regardless, no rush!

whether it would stay as a patch here or be upstreamed.

If we do it then definitely worth opening a PR, regardless of whether they merge it

if the esbuild exclude hack is okay to keep or if I should find a better way.

I'll take a look 👀

@ghostdevv

Copy link
Copy Markdown
Member

Oh and another thing that came to mind is whether people expect the rss/atom/json feed urls to be on the root, or whether we should make it /blog/...

@Kiwow

Kiwow commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

His isn't valid JSON currently 🤔

Huh, yeah, that's weird. Well, JSON feed is still definitely niche, but for what it's worth, I found out that Declan Childlow also uses it.

I opened a PR to add version 1.1 support in the upstream feed package and I quickly locally tested it with pnpm link. It seems to correctly generate all authors for each post, so if we decide to move forward with JSON feeds, we can use a similar link/patch setup or use a newer version of the upstream if it gets merged. If there's no interest for a JSON feed, no worries, I'm cool with potentially contributing to the feed package either way :)

Oh and another thing that came to mind is whether people expect the rss/atom/json feed urls to be on the root, or whether we should make it /blog/...

I think either makes sense, honestly. https://npmx.dev/rss.xml right now redirects to https://npmx.dev/package/rss.xml, but I wouldn't call that a conflict if that redirect gets replaced with the feed.

Out of the sites with feeds that come to mind, MDN seems the most similar to npmx in that they have a blog with a feed, but it's by no means the main reason people visit the site. Their feed is under /blog (at https://developer.mozilla.org/en-US/blog/rss.xml) and they include a link[rel="alternate"] on every page. This is pretty much exactly the opposite of what I've done 😅 – feed paths directly under / but getting advertised as "alternate" only under /blog.

I took a look at other sites whose feeds I subscribe to and the majority of them have a /feed.xml or /rss.xml, but it's hard to compare.

@ghostdevv

Copy link
Copy Markdown
Member

and they include a link[rel="alternate"] on every page. This is pretty much exactly the opposite of what I've done 😅 – feed paths directly under / but getting advertised as "alternate" only under /blog.

Hmm, I think I lean towards putting the feeds under /blog to reduce the amount of stuff the root. I'll see what others think though 👀 Regardless, putting the rel alternative on the /blog pages is good! We could also consider putting it on the / to help with discoverability

@ghostdevv ghostdevv added the needs discussion An idea that needs more discussion to understand the scope and impact. label Jul 6, 2026
Kiwow added 2 commits July 9, 2026 18:58
 Conflicts:
	nuxt.config.ts
	package.json
	pnpm-lock.yaml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs discussion An idea that needs more discussion to understand the scope and impact.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RSS support for the blog

3 participants