Skip to content

fix(web): keep RSC payloads out of the browser's HTTP cache (web 0.65.1) - #640

Merged
kroqdotdev merged 2 commits into
mainfrom
fix/rsc-payload-browser-cache
Nov 10, 2026
Merged

kroqdotdev merged 2 commits into
mainfrom
fix/rsc-payload-browser-cache

Conversation

@kroqdotdev

@kroqdotdev kroqdotdev commented Nov 10, 2026 •

Copy link
Copy Markdown
Owner

Problem

Right after a deploy, the first client-side navigation to a prerendered page could crash into the app error boundary with React error #306 ("Element type is invalid. Received a promise that resolves to: undefined"). Reported once, on the 0.63.0 deploy, for a dashboard to landing page click in a Chromium browser; a reload fixed it.

Cause

  1. Next marks prerendered pages s-maxage=600, stale-while-revalidate=31535400 with no max-age, and the router's ?_rsc= URL is a hash of request headers only, so it is identical across builds. Chromium's private HTTP cache honours the stale-while-revalidate window and serves the previous build's RSC payload once.
  2. Next rejects that payload on its build id, but the Flight client has already preloaded the chunks it names (still in the browser cache, they are immutable).
  3. Turbopack's runtime keeps the first factory it registers for a module id. A module both builds share (components/landing/pricing-cta.tsx) stays the old version.
  4. The fresh payload (from a parallel prefetch) asks for PaidPlanCTA, new in 0.63.0, which the old factory never exports, so the client reference resolves to undefined: chore(deps): bump quinn-proto from 0.11.14 to 0.11.16 in /apps/receiver-rs #306.

Reproduced locally with production builds of 0.62.9 and 0.63.0: visit on the old build, swap servers, reload the dashboard, click the logo, and the error page shows with #306.

Fix

next.config.ts sets Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate on every request with RSC: 1 (prefetch or navigation; Next compiles /:path* so it matches / too). Next only sets Cache-Control when none is set, so this wins. Page HTML keeps Next's own header; Cloudflare already treats these responses as dynamic, and Next's server-side ISR cache does not depend on the header.

With the fix in both builds, the same old-to-new swap renders the landing page with no errors.

Note: browsers already holding 0.63.0 payloads can still hit this once on the first navigation after this release deploys; from the following deploy on it is covered.

Tests

  • next.config.test.ts: RSC requests (a navigation and the segment cache's /_tree prefetch) to /, /dashboard, /docs/agents, /webhooks/stripe get the no-store header; page HTML and email templates keep their current Cache-Control. The helper now honours has conditions and compiles sources with modifyRouteRegex, as Next's router does. Without the fix 4 tests fail.
  • Typecheck and lint clean.

Web 0.63.1 with a changelog entry.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 13695ba1-82e1-4f8b-a24b-70e6d75a6dfb
  • Autofix · 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

kroqdotdev and others added 2 commits November 10, 2026 15:43
After a deploy, the first client navigation to a prerendered page could
crash with React error #306. Next marks prerendered pages
stale-while-revalidate for a year with no max-age, and the router's
_rsc URL depends only on request headers, so Chromium served the
previous build's RSC payload from its HTTP cache. Next rejects it on
the build id, but decoding it has already loaded the old build's
chunks, and Turbopack keeps the first factory it registers for a module
id. A module both builds share stayed the old version, so a new export
of it (PaidPlanCTA) resolved to undefined.

Every RSC request now gets private, no-store Cache-Control; page HTML
keeps Next's own header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test helper now compiles sources with modifyRouteRegex, as the
router does, so "/:path*" matches the root and the RSC rule needs no
separate "/" entry. The prefetch case sends the segment cache's route
tree prefetch, the request that brought in the stale payload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kroqdotdev kroqdotdev changed the title fix(web): keep RSC payloads out of the browser's HTTP cache (web 0.63.1) fix(web): keep RSC payloads out of the browser's HTTP cache (web 0.65.1) Nov 10, 2026
@kroqdotdev
kroqdotdev force-pushed the fix/rsc-payload-browser-cache branch from f62e415 to b111467 Compare November 10, 2026 14:44
@github-actions

Copy link
Copy Markdown

⚠️ Deprecation Warning: The deny-licenses option is deprecated for possible removal in the next major release. For more information, see issue 997.

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@kroqdotdev
kroqdotdev merged commit 3acdeb3 into main Nov 10, 2026
29 checks passed
@kroqdotdev
kroqdotdev deleted the fix/rsc-payload-browser-cache branch November 10, 2026 14:57
@github-actions github-actions Bot locked and limited conversation to collaborators Nov 10, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant