fix(router-core): replace fatal invariant with null-check in SPA mode hydration#6814
fix(router-core): replace fatal invariant with null-check in SPA mode hydration#6814sleitor wants to merge 3 commits intoTanStack:mainfrom
Conversation
… hydration When hydrating a 404/error page in SPA mode, matches[1] can be undefined (only the root match exists). The previous code used a fatal invariant that threw an uncaught error, causing a white screen of death. Replace with a graceful null-check and console.warn, matching the intent of the ESM build behaviour described in TanStack#6806. Fixes TanStack#6806
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx run @benchmarks/client-nav:test:perf:solid |
❌ Failed | 1m 13s | View ↗ |
nx run @benchmarks/ssr:test:perf:vue |
✅ Succeeded | 2m 15s | View ↗ |
nx run @benchmarks/ssr:test:perf:solid |
✅ Succeeded | 1m 59s | View ↗ |
nx run @benchmarks/ssr:test:perf:react |
✅ Succeeded | 2m 5s | View ↗ |
nx run @benchmarks/client-nav:test:perf:vue |
✅ Succeeded | 1m 23s | View ↗ |
nx run @benchmarks/client-nav:test:perf:react |
✅ Succeeded | 7m 27s | View ↗ |
☁️ Nx Cloud last updated this comment at 2026-03-03 20:51:06 UTC
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review infoConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughReplaces a fatal invariant check with a conditional guard in SPA hydration within the SSR client. If no child match exists during hydration (e.g., 404/error pages), the code now logs a warning and skips pending-state setup instead of throwing. Changes
Sequence Diagram(s)(omitted — change is targeted to a single-file hydration guard and does not introduce multi-component sequential flow warranting a diagram) Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). 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. Comment |
Bundle Size Benchmarks
Trend sparkline is historical gzip bytes ending with this PR measurement; lower is better. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/router-core/src/ssr/ssr-client.ts`:
- Around line 267-289: The code sets match._nonReactive.displayPendingPromise
but later clears only match.displayPendingPromise via router.updateMatch,
leaving match._nonReactive.displayPendingPromise stale; update the clearing
logic to clear the same property you set
(match._nonReactive.displayPendingPromise) as well as the public field to keep
both representations in sync—modify the router.updateMatch callback (or
follow-up mutation) to unset _displayPending and both displayPendingPromise and
_nonReactive.displayPendingPendingPromise (or directly clear
match._nonReactive.displayPendingPromise) and ensure loadPromise is
assigned/cleared on the same object used when setting it.

Summary
Fixes #6806
Problem
When hydrating a 404/error page in SPA mode,
matches[1]can beundefined(only the root match exists). The code previously used a fatalinvariant()that threw an uncaught error in this situation, causing a white screen of death.This is particularly problematic in CJS environments (Cloudflare Pages/Wrangler, certain Node.js SSR setups) where the crash surfaces directly, but the underlying source is the same.
Fix
Replace the fatal
invariantwith a graceful null-check and aconsole.warn, matching what was described in the issue. When no child match is found below the root in SPA mode, we warn and skip setting up the pending state —router.load()will handle the route resolution on its own.Summary by CodeRabbit