Skip to content

fix(cloudflare): do not let browsers cache a 404 from public assets - #4727

Merged
pi0 merged 2 commits into
nitrojs:mainfrom
charan-rathore:fix-cf-assets-404-cache-4648
Oct 13, 2026
Merged

pi0 merged 2 commits into
nitrojs:mainfrom
charan-rathore:fix-cf-assets-404-cache-4648

Conversation

@charan-rathore

Copy link
Copy Markdown

🔗 Linked issue

Resolves #4648

❓ Type of change

  • 📖 Documentation (updates to the documentation, readme, or JSdoc annotations)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

For a public assets base with maxAge (Nuxt's /_nuxt/ is one), nitro writes a _headers rule with cache-control: public, max-age=<maxAge>, immutable. The cloudflare-module entry hands every path under such a base to env.ASSETS.fetch() and returns the result as it is. Cloudflare's asset layer applies the _headers rule to a 404 as well, so a missing file comes back as 404 with the one year immutable header. A 404 with an explicit max-age is cacheable, so the browser keeps "this file does not exist" until the user does a hard reload. This is the Failed to fetch dynamically imported module case described in #4648. Because the entry answers before h3 runs, route rules and server middleware cannot change it.

This PR changes src/presets/cloudflare/runtime/cloudflare-module.ts. When env.ASSETS.fetch() returns a 404 whose cache-control has a positive max-age or immutable, the entry returns a copy of the response with cache-control: no-store. Successful asset responses and 404s without a long lived cache policy are passed through untouched.

Test: test/presets/cloudflare-module.test.ts runs the built fixture in Miniflare with the ASSETS binding. It requests an existing file under the fixture's /build base (expects 200 and max-age=3600) and a missing one (expects 404 and no-store). The missing file case fails on main with public, max-age=3600, immutable and passes with this change.

What I ran:

  • pnpm fmt, pnpm lint and pnpm typecheck: clean.
  • vitest run test/presets/cloudflare-module.test.ts test/presets/cloudflare-pages.test.ts test/unit: 601 passed, 1 failed. The failure is virtual-routing.test.ts > node format handler interop > receives the real Request when registered as a fetch object, and it fails the same way on an untouched main.

What I did not run: a real Cloudflare deployment or wrangler dev with a Nuxt app. The full pnpm test pipeline was not run.

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

The _headers rule of a public assets base also applies to a missing file returned by env.ASSETS.fetch(). Answer such a 404 with cache-control: no-store.

Signed-off-by: Charan Rathore <[email protected]>
@vercel

vercel Bot commented Oct 5, 2026

Copy link
Copy Markdown

@charan-rathore is attempting to deploy a commit to the Nitro Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c0360efc-c977-4729-bae5-ffb17b624641
📥 Commits

Reviewing files that changed from the base of the PR and between 84c5fb5 and 0375ae8.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 444d9f11-412e-46e9-9a4d-3d13bd82e669
📥 Commits

Reviewing files that changed from the base of the PR and between c7bec88 and 84c5fb5.

📒 Files selected for processing (2)
  • src/presets/cloudflare/runtime/cloudflare-module.ts
  • test/presets/cloudflare-module.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

📝 Walkthrough

The Cloudflare module now changes the cache header on asset 404 responses when the existing header matches a positive max-age or immutable. A Miniflare test checks the status and cache header for found and missing assets.

Changes: Cloudflare asset 404 cache handling

Layer / File(s) Summary
Asset fallback and response checks
src/presets/cloudflare/runtime/cloudflare-module.ts, test/presets/cloudflare-module.test.ts
The asset fallback sets cache-control: no-store on qualifying 404 responses and returns other responses unchanged. The Miniflare test checks a found asset and a missing asset, and disposes of Miniflare in a finally block.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~8 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 84c5f

The Cloudflare asset 404 change is covered by the new test. No issue identified here prevents merging after normal checks.

### Architecture Summary

Architecture risk: 🔵 Low · up to 84c5f

The change affects 2 systems.

Changed systems: src, test

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — src (service) was modified; 1 changed file maps to changed impact.
  • observed — test (service) was modified; 1 changed file maps to changed impact.

Before / after behavior

  • observed — Modified behavior in src/presets/cloudflare/runtime/cloudflare-module.ts: The asset fallback now awaits the asset fetch. For a 404 whose cache-control header matches /max-age=[1-9]|immutable/i, it returns a response copy with cache-control set to no-store; otherwise it returns the original response. Previously, the fallback returned the fetch promise directly without modifying 404 caching headers.
  • observed — Modified behavior in test/presets/cloudflare-module.test.ts: Adds a Miniflare test with the ASSETS binding that checks the cache headers and status for a found public asset and a missing asset, then disposes of Miniflare in all cases.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title follows the Conventional Commits format with type fix, scope cloudflare, and a description that matches the change.
Description check ✅ Passed The description explains the Cloudflare public-asset 404 cache issue, the fix, and the test coverage.
Linked Issues check ✅ Passed Issue #4648 requires cloudflare-module to prevent a 404 from a public asset from retaining a long-lived cache policy. The fetch handler now detects 404 responses with positive max-age or `immutabl…
Out of Scope Changes check ✅ Passed The changes are limited to the cloudflare-module asset response and a test for that behavior. Both changes directly support issue #4648. The PR does not change the unverified cloudflare-pages beha…
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2…
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@pkg-pr-new

pkg-pr-new Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/nitro@4727

commit: 0375ae8

@pi0x pi0x added the v3 label Oct 7, 2026
@pi0
pi0 merged commit 17797f8 into nitrojs:main Oct 13, 2026
12 of 14 checks passed
@pi0x pi0x removed the v3 label Nov 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cloudflare-module: 404 from env.ASSETS.fetch inherits the immutable _headers rule of its public-assets base, so browsers cache missing chunks for a year

3 participants