Fix dev mode reload and error on back navigation between Next.js pages (#93486)
Follow-up to #92892, addressing the regression reported in
https://github.com/vercel/next.js/issues/93136#issuecomment-4298124826.
That PR persisted the dev-mode debug channel chunks under a single
shared `sessionStorage` key (`__next_debug_channel`) so the data could
be replayed when a page is served from HTTP cache. The single-key design
holds when the only navigations away from a Next.js page go to other
origins, but it breaks the moment two Next.js documents share the same
tab.
When a user does an MPA navigation from page A to page B (a regular `<a
href>` link to another Next.js route), B's persisted entry overwrites
A's under the same key. Clicking back restores A from HTTP cache; A's
bootstrap re-executes with its original `self.__next_r`, looks up
`sessionStorage`, finds B's `requestId`, returns `undefined` from
`restoreDebugChannelFromSessionStorage`, and falls back to
`location.reload()`. Before the reload completes, the main RSC stream
closes; the Flight client surfaces references to debug-channel chunks
that block other model chunks — references the debug channel was
supposed to deliver but never did — as a `Connection closed.` error,
briefly flashing the error UI.
The fix changes the storage key from `__next_debug_channel` to a
per-document prefix `__next_debug_channel:${requestId}`. Each initial
document persists into its own entry, so A's data survives B's load and
the back-restore finds it. `requestId` is dropped from the stored value
since it's now part of the key. On `setItem` failure (likely a quota
issue), other entries with the prefix are removed and `setItem` is
retried once; if it still fails, persistence is silently skipped and
`location.reload()` remains the safety net on the read side.
The fallback path also gets a small change: when restore legitimately
fails (e.g., session storage cleared mid-session), `createDebugChannel`
now returns `{ readable: new ReadableStream() }` rather than `{
readable: undefined }`. The empty stream never enqueues and never
closes, so from the Flight client's perspective the debug channel is
still active when the main stream closes — the unresolved chunk
references no longer surface as a synchronous error before
`location.reload()` tears the document down.
The `bfcache-regression` test was updated to reproduce the bug. It now
clicks an internal MPA link to `/target-page` instead of an external
one, and asserts no console errors after the round trip via
`assertNoConsoleErrors`. The fixture pre-warms `/target-page`
compilation in parallel with the browser load in webpack mode, to avoid
an unrelated Fast Refresh full-reload warning when the route is compiled
on demand.