mirror of
https://github.com/cloudflare/vinext.git
synced 2026-05-09 00:09:23 +02:00
[PR #875] [MERGED] fix(app-router): hard-navigate to browser URL on non-ok RSC fetch #912
Labels
No labels
enhancement
enhancement
good first issue
help wanted
nextjs-tracking
nextjs-tracking
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/vinext#912
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/cloudflare/vinext/pull/875
Author: @NathanDrake2406
Created: 4/23/2026
Status: ✅ Merged
Merged: 4/24/2026
Merged by: @james-elicx
Base:
main← Head:fix/rsc-fetch-not-ok-hard-nav📝 Commits (10+)
4ef63f0fix(app-router): hard-navigate to browser URL on non-ok RSC fetch6071901fix(app-router): break reload loop when initial RSC fetch is persistently non-ok97d2942fix(app-router): harden initial RSC recovery and cover non-RSC content-types6d0f2e7test(app-router): narrow RSC error filter to stream-parse diagnostics23796b6fix(app-router): abort hydration bootstrap when recovery cannot restore RSC88e14e7fix(app-router): tighten RSC error hygiene around hydration and unloadff52909test(app-router): tighten RSC fetch error suitee711509fix(app-router): reset isPageUnloading on pageshow for bfcache restore594fce4refactor(app-router): unify recoveryFromBadInitialRscResponse return and fix test listener orderingc0270f3refactor(app-router): hard-nav to response URL and isolate hydration bootstrap📊 Changes
2 files changed (+396 additions, -6 deletions)
View changed files
📝
packages/vinext/src/server/app-browser-entry.ts(+174 -6)➕
tests/e2e/app-router/rsc-fetch-errors.spec.ts(+222 -0)📄 Description
What this changes
When a client-side RSC navigation fetch returns a non-ok response (404, 500, or any other HTTP error), the client now immediately hard-navigates to the browser-facing URL instead of trying to parse the error body as an RSC stream.
The same guard is added to the initial hydration fallback fetch in
readInitialRscStream: a non-ok response triggerswindow.location.reload()so the server can render the correct error page as HTML.Why
Without this fix, a non-ok RSC fetch (e.g. a server returning an HTML error page with status 500) is passed directly to
createFromFetch. The RSC parser tries to read HTML as an RSC stream, throws a cryptic"Connection closed"error, and the outer catch logs"[vinext] RSC navigation error: ..."then hard-navigates to the same URL, which can loop.The fix matches Next.js behavior from
packages/next/src/client/components/router-reducer/fetch-server-response.ts:211:Approach
In the navigation path (inside
navigateRsc): added!navResponse.okcheck after the stale-navigation guard. On non-ok: settle pending router state, clear pending pathname, andwindow.location.href = href(the browser URL without.rscsuffix). The stale-navigation check atnavId !== activeNavigationIdruns first so superseded navigations never trigger a hard-nav to a stale URL.In
readInitialRscStream: added!rscResponse.okcheck after the fallback fetch. On non-ok:window.location.reload()and return a never-resolving stream (so the caller does not proceed intocreateFromReadableStreamduring the brief window before the reload takes effect).No changes to the prefetch path --
prefetchRscResponsealready discards non-ok responses before storing snapshots.Validation
New E2E tests in
tests/e2e/app-router/rsc-fetch-errors.spec.ts:!ok) hard-navs to the non-.rscURL.rscAll 3 new tests pass. Existing navigation, regression, and error-handling E2E test suites pass (1 pre-existing flaky test on retry, unrelated to this change).
Risks / follow-ups
The
readInitialRscStreamnon-ok path useswindow.location.reload(), which will loop if the server consistently returns non-ok for the page's RSC endpoint. This is the same behavior as a hard browser refresh on a broken server and is not worse than the pre-fix behavior (opaque parse failure).🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.