[PR #1028] [MERGED] fix(navigation): hard-nav to target when render error tears down the tree #1031

Closed
opened 2026-05-06 13:11:39 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/1028
Author: @james-elicx
Created: 5/2/2026
Status: Merged
Merged: 5/2/2026
Merged by: @james-elicx

Base: mainHead: fix/navigation-rsc-error-recovery


📝 Commits (1)

  • d07a965 fix(navigation): hard-nav to target when render error tears down the tree

📊 Changes

3 files changed (+126 additions, -3 deletions)

View changed files

📝 packages/vinext/src/server/app-browser-entry.ts (+21 -2)
📝 packages/vinext/src/server/app-browser-error.ts (+23 -0)
📝 tests/app-browser-entry.test.ts (+82 -1)

📄 Description

Summary

  • When an RSC payload contains an error reference for a slot (e.g. a server component throws), createPendingNavigationCommit only awaits the top-level elements promise — it doesn't see the rejected nested reference, so the dispatch goes through.
  • React then renders, the slot throws when use() unwraps the rejected reference, and without an error.tsx boundary above NavigationCommitSignal the entire root unmounts.
  • The URL update lives in NavigationCommitSignal's pre-paint layout effect, which never runs when the commit fails. Result: stale URL, blank page, console-only error.

Fix

  • Track the in-flight navigation target in a module-level pendingNavigationRecoveryHref. Set it just before dispatchBrowserTree; clear it once the commit effect updates history (and on the synchronous catch path).
  • Add an onUncaughtError handler to hydrateRoot that reads the recovery href at fire time and window.location.assign()s to it. The user lands on the URL they clicked and the server can render its actual error UI for that route.
  • Wire the handler in both dev and prod (previously only onCaughtError was registered, dev-only).

Tests

  • Four new unit tests in tests/app-browser-entry.test.ts cover: hard-nav on pending recovery href, no-op when nothing is pending (initial hydration error), error + component stack always logged, lazy read so newer navigations win.
  • Full unit suite: 3399 passing.

Test plan

  • Click a link to a route whose server component throws — browser should land on the new URL with the server error visible, instead of staying on the old URL with a blank page.
  • Normal navigation to a working route is unaffected.
  • Initial hydration errors (no pending navigation) still surface to the console without a spurious redirect.

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/1028 **Author:** [@james-elicx](https://github.com/james-elicx) **Created:** 5/2/2026 **Status:** ✅ Merged **Merged:** 5/2/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `fix/navigation-rsc-error-recovery` --- ### 📝 Commits (1) - [`d07a965`](https://github.com/cloudflare/vinext/commit/d07a96531ab99dae2c661ae91bce61fa34e1f3e2) fix(navigation): hard-nav to target when render error tears down the tree ### 📊 Changes **3 files changed** (+126 additions, -3 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/server/app-browser-entry.ts` (+21 -2) 📝 `packages/vinext/src/server/app-browser-error.ts` (+23 -0) 📝 `tests/app-browser-entry.test.ts` (+82 -1) </details> ### 📄 Description ## Summary - When an RSC payload contains an error reference for a slot (e.g. a server component throws), `createPendingNavigationCommit` only awaits the top-level elements promise — it doesn't see the rejected nested reference, so the dispatch goes through. - React then renders, the slot throws when `use()` unwraps the rejected reference, and without an `error.tsx` boundary above `NavigationCommitSignal` the entire root unmounts. - The URL update lives in `NavigationCommitSignal`'s pre-paint layout effect, which never runs when the commit fails. Result: stale URL, blank page, console-only error. ## Fix - Track the in-flight navigation target in a module-level `pendingNavigationRecoveryHref`. Set it just before `dispatchBrowserTree`; clear it once the commit effect updates history (and on the synchronous catch path). - Add an `onUncaughtError` handler to `hydrateRoot` that reads the recovery href at fire time and `window.location.assign()`s to it. The user lands on the URL they clicked and the server can render its actual error UI for that route. - Wire the handler in both dev and prod (previously only `onCaughtError` was registered, dev-only). ## Tests - Four new unit tests in `tests/app-browser-entry.test.ts` cover: hard-nav on pending recovery href, no-op when nothing is pending (initial hydration error), error + component stack always logged, lazy read so newer navigations win. - Full unit suite: 3399 passing. ## Test plan - [ ] Click a link to a route whose server component throws — browser should land on the new URL with the server error visible, instead of staying on the old URL with a blank page. - [ ] Normal navigation to a working route is unaffected. - [ ] Initial hydration errors (no pending navigation) still surface to the console without a spurious redirect. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:11:39 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#1031
No description provided.