Skip to content

fix(nitro): add json extension to payload cache items#35043

Merged
danielroe merged 2 commits into
nuxt:mainfrom
Tofandel:patch-3
May 11, 2026
Merged

fix(nitro): add json extension to payload cache items#35043
danielroe merged 2 commits into
nuxt:mainfrom
Tofandel:patch-3

Conversation

@Tofandel

@Tofandel Tofandel commented May 11, 2026

Copy link
Copy Markdown
Contributor

This fixes #34961 which is caused by the url being used as is to store the cache items. The problem is urls can have nesting (eg: /mypath, /mypath/mysubpath)

🔗 Linked issue

resolves #34961
unjs/unstorage#775

📚 Description

This fixes #34961 which is caused by the url being used as is to store the cache items. The problem is urls can have content at each nesting level (eg: /mypath, /mypath/mysubpath) but a filesystem cannot

Since the fs driver does not differentiate between folders and files. This means /mypath becomes a file with the cache content and when you try to write /mypath/mysubpath it fails because /mypath is a file and not a directory.

With this PR there is no conflict issue because
/mypath will store as /mypath.json
/mypath/mysubpath will store as /mypath/mysubpath.json

This should be backported to both 3.x and 4.x as this issue is wrecking havock on both versions since this feature was introduced.

I am not sure if the cache would be cleared automatically after a new build, so anyone affected might need to clear the cache directory manually.

Another option would be to use the sha-256 hash of the url as the cache key as is done in the other cache driver. This would avoid the issue of the cache needing to be cleared as well. And will avoid any kind of issues with fs path normalization.

This fixes nuxt#34961 which is caused by the url being used as is to store the cache items. The problem is urls can have nesting (eg: `/mypath`, `/mypath/mysubpath`)

But the fs driver does not differentiate between folders and files. This means /mypath become a file with the cache content and when you try to write /mypath/mysubpath it fails because /mypath is a file and not a directory.

With this PR:
/mypath will store as /mypath.json
/mypath/mysubpath will store as /mypath/mysubpath.json

There is no conflict issue
@Tofandel Tofandel requested a review from danielroe as a code owner May 11, 2026 13:23
@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@github-actions github-actions Bot added 5.x 🐛 bug Something isn't working as expected labels May 11, 2026
@Tofandel Tofandel changed the title fix(nitro-server): Add json extension to payload cache items fix(nitro): Add json extension to payload cache items May 11, 2026
@pkg-pr-new

pkg-pr-new Bot commented May 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@nuxt/kit

npm i https://pkg.pr.new/@nuxt/kit@35043

@nuxt/nitro-server

npm i https://pkg.pr.new/@nuxt/nitro-server@35043

nuxt

npm i https://pkg.pr.new/nuxt@35043

@nuxt/rspack-builder

npm i https://pkg.pr.new/@nuxt/rspack-builder@35043

@nuxt/schema

npm i https://pkg.pr.new/@nuxt/schema@35043

@nuxt/vite-builder

npm i https://pkg.pr.new/@nuxt/vite-builder@35043

@nuxt/webpack-builder

npm i https://pkg.pr.new/@nuxt/webpack-builder@35043

commit: 6445458

@coderabbitai

This comment has been minimized.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/nitro-server/src/runtime/handlers/renderer.ts (1)

184-186: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Cache key mismatch – line 185 must also use .json extension.

Line 185 writes the payload to cache using ssrContext.url without the .json extension, but lines 127–128 read using url + '.json'. This mismatch means payloads cached via this path will never be retrieved.

Scenario:

  1. First request to /mypath/_payload.json during prerender
  2. Line 127 checks cache for /mypath.json → miss
  3. Payload is rendered and cached at line 185 as /mypath (no extension)
  4. Subsequent request to /mypath/_payload.json
  5. Line 127 checks cache for /mypath.json → miss (cache entry is /mypath)
🐛 Proposed fix to align cache key with read path
     const response = renderPayloadResponse(ssrContext)
     if (import.meta.prerender) {
-      await payloadCache!.setItem(ssrContext.url, response)
+      await payloadCache!.setItem(ssrContext.url + '.json', response)
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nitro-server/src/runtime/handlers/renderer.ts` around lines 184 -
186, Cached payloads are written with a key that omits the ".json" extension
which doesn't match the read path that looks up url + '.json'; update the write
in the import.meta.prerender branch to use the same cache key format (append
".json" to ssrContext.url when calling payloadCache!.setItem) so
payloadCache.setItem(ssrContext.url + '.json', response) aligns with the lookup
logic used elsewhere (where url + '.json' is read).
🧹 Nitpick comments (1)
packages/nitro-server/src/runtime/handlers/renderer.ts (1)

195-195: ⚡ Quick win

Verify cache key normalisation consistency across read and write paths.

The write path normalises the URL (removes trailing slashes except for root) before appending .json, whilst the read path on line 124 extracts the base URL from the payload request path. Whilst these should produce matching keys in normal cases, consider extracting the normalisation logic into a helper function to ensure consistency and prevent future drift.

Example helper:

function normalizePayloadCacheKey(url: string): string {
  const normalized = url === '/' ? '/' : url.replace(/\/$/, '')
  return normalized + '.json'
}

This would make both paths use the same logic and improve maintainability.

Additionally, run the following verification script to check for any other payloadCache operations that might need updating:

#!/bin/bash
# Description: Find all payloadCache operations to ensure complete .json migration

# Search for all payloadCache method calls
rg -n -C3 'payloadCache\s*!\s*\.\s*(setItem|getItem|hasItem|removeItem)' --type=ts
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nitro-server/src/runtime/handlers/renderer.ts` at line 195, The
write path for payloadCache uses inline URL normalization which may diverge from
the read path; extract the normalization into a single helper (e.g.
normalizePayloadCacheKey) that takes ssrContext.url (or the incoming request
path) and returns the normalized key with the ".json" suffix, then replace the
inline logic in the write site (where renderPayloadResponse is stored via
payloadCache!.setItem) and in the read path (the code that calls
payloadCache!.getItem / hasItem / removeItem) to call this helper so both use
identical logic; also search for all payloadCache usages
(setItem/getItem/hasItem/removeItem) and update them to use the helper (you can
run the provided ripgrep script to find occurrences).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/nitro-server/src/runtime/handlers/renderer.ts`:
- Around line 184-186: Cached payloads are written with a key that omits the
".json" extension which doesn't match the read path that looks up url + '.json';
update the write in the import.meta.prerender branch to use the same cache key
format (append ".json" to ssrContext.url when calling payloadCache!.setItem) so
payloadCache.setItem(ssrContext.url + '.json', response) aligns with the lookup
logic used elsewhere (where url + '.json' is read).

---

Nitpick comments:
In `@packages/nitro-server/src/runtime/handlers/renderer.ts`:
- Line 195: The write path for payloadCache uses inline URL normalization which
may diverge from the read path; extract the normalization into a single helper
(e.g. normalizePayloadCacheKey) that takes ssrContext.url (or the incoming
request path) and returns the normalized key with the ".json" suffix, then
replace the inline logic in the write site (where renderPayloadResponse is
stored via payloadCache!.setItem) and in the read path (the code that calls
payloadCache!.getItem / hasItem / removeItem) to call this helper so both use
identical logic; also search for all payloadCache usages
(setItem/getItem/hasItem/removeItem) and update them to use the helper (you can
run the provided ripgrep script to find occurrences).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3deea341-087d-46d2-8382-a141f30738d7

📥 Commits

Reviewing files that changed from the base of the PR and between 68ebb99 and 0634650.

📒 Files selected for processing (1)
  • packages/nitro-server/src/runtime/handlers/renderer.ts

@Tofandel Tofandel changed the title fix(nitro): Add json extension to payload cache items fix(nitro): add json extension to payload cache items May 11, 2026
@codspeed-hq

codspeed-hq Bot commented May 11, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 20 untouched benchmarks
⏩ 3 skipped benchmarks1


Comparing Tofandel:patch-3 (6445458) with main (68ebb99)

Open in CodSpeed

Footnotes

  1. 3 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@danielroe danielroe added this pull request to the merge queue May 11, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 11, 2026
@danielroe danielroe merged commit 819a00e into nuxt:main May 11, 2026
36 checks passed
@github-actions github-actions Bot mentioned this pull request May 11, 2026
4 tasks
@github-actions github-actions Bot mentioned this pull request May 11, 2026
@github-actions github-actions Bot mentioned this pull request May 13, 2026
davidstackio pushed a commit to davidstackio/nuxt that referenced this pull request May 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

5.x 🐛 bug Something isn't working as expected

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[nuxt] ENOTDIR in dev: payload cache for / route written as flat file, blocks every other route's payload write

2 participants