Skip to content

fix(router-core): replace fatal invariant with null-check in SPA mode hydration#6814

Open
sleitor wants to merge 3 commits intoTanStack:mainfrom
sleitor:fix-6806
Open

fix(router-core): replace fatal invariant with null-check in SPA mode hydration#6814
sleitor wants to merge 3 commits intoTanStack:mainfrom
sleitor:fix-6806

Conversation

@sleitor
Copy link
Contributor

@sleitor sleitor commented Mar 3, 2026

Summary

Fixes #6806

Problem

When hydrating a 404/error page in SPA mode, matches[1] can be undefined (only the root match exists). The code previously used a fatal invariant() 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 invariant with a graceful null-check and a console.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.

-    invariant(
-      match,
-      'Expected to find a match below the root match in SPA mode.',
-    )
-    setMatchForcePending(match)
-    ...
+    if (!match) {
+      console.warn(
+        'SPA hydration: no child match found below root match. This can happen during 404/error page hydration.',
+      )
+    } else {
+      setMatchForcePending(match)
+      ...
+    }

Summary by CodeRabbit

  • Bug Fixes
    • Improved SPA hydration to avoid crashes when loading error pages or unavailable routes (e.g., root-only/404 cases).
    • Prevented incorrect pending-state behavior and flicker when no child route is present, ensuring loading indicators only appear when appropriate.
    • Added safer handling that skips irrelevant pending logic during initialization.

… 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
@nx-cloud
Copy link

nx-cloud bot commented Mar 3, 2026

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit f1f10f7

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f098bef and f1f10f7.

📒 Files selected for processing (1)
  • packages/router-core/src/ssr/ssr-client.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/router-core/src/ssr/ssr-client.ts

📝 Walkthrough

Walkthrough

Replaces 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

Cohort / File(s) Summary
SPA Hydration Guard
packages/router-core/src/ssr/ssr-client.ts
Replaces an invariant that assumed a child match with a null-check. Moves pending-state wiring, _displayPending flag, and loadPromise.then handling inside the conditional; logs a warning and skips pending setup when no child match is present to avoid crashes during SPA hydration on 404/error routes.

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

  • schiller-manuel
  • nlynzaad

Poem

🐰 I hopped through routes both near and far,
Found a missing child — no fatal spar.
I warned, I skipped, kept the page alive,
No white-screen tumble — the app can thrive! 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: replacing a fatal invariant check with a null-check in SPA mode hydration to fix a crash on 404/error pages.
Linked Issues check ✅ Passed The PR fully addresses issue #6806 by replacing the fatal invariant with a null-check and console.warn, handling missing child matches gracefully in SPA mode hydration.
Out of Scope Changes check ✅ Passed All changes in ssr-client.ts are directly related to fixing the invariant issue described in #6806; no unrelated modifications are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Mar 3, 2026

Bundle Size Benchmarks

  • Commit: 2217f7c3f19f
  • Measured at: 2026-03-03T20:31:10.188Z
  • Baseline source: history:2217f7c3f19f
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Raw Brotli Trend
react-router.minimal 86.58 KiB 0 B (0.00%) 272.45 KiB 75.22 KiB ▅▅▅▅▅▅▅▅▅▅▅
react-router.full 89.61 KiB 0 B (0.00%) 282.78 KiB 77.90 KiB ▅▅▅▅▅▅▅▅▅▅▅
solid-router.minimal 35.88 KiB 0 B (0.00%) 107.56 KiB 32.26 KiB ▅▅▅▅▅▅▅▅▅▅▅
solid-router.full 40.21 KiB 0 B (0.00%) 120.61 KiB 36.13 KiB ▅▅▅▅▅▅▅▅▅▅▅
vue-router.minimal 51.75 KiB 0 B (0.00%) 147.54 KiB 46.50 KiB ▅▅▅▅▅▅▅▅▅▅▅
vue-router.full 56.55 KiB 0 B (0.00%) 163.12 KiB 50.86 KiB ▅▅▅▅▅▅▅▅▅▅▅
react-start.minimal 99.17 KiB +61 B (+0.06%) 311.71 KiB 85.82 KiB ▁▁▁▁▁▁▁▁▁▁▁█
react-start.full 102.56 KiB +65 B (+0.06%) 321.52 KiB 88.64 KiB ▁▁▁▂▂▂▂▂▂▂▂█
solid-start.minimal 48.24 KiB +52 B (+0.11%) 145.26 KiB 42.70 KiB ▁▁▁▁▁▁▁▁▁▁▁█
solid-start.full 53.74 KiB +58 B (+0.11%) 161.21 KiB 47.36 KiB ▁▁▁▂▂▂▂▂▂▂▂█

Trend sparkline is historical gzip bytes ending with this PR measurement; lower is better.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 3, 2026

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@6814

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@6814

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@6814

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@6814

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@6814

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@6814

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@6814

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@6814

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@6814

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@6814

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@6814

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@6814

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@6814

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@6814

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@6814

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@6814

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@6814

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@6814

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@6814

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@6814

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@6814

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@6814

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@6814

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@6814

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@6814

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@6814

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@6814

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@6814

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@6814

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@6814

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@6814

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@6814

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@6814

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@6814

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@6814

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@6814

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@6814

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@6814

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@6814

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@6814

commit: 2ffbcff

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2217f7c and f098bef.

📒 Files selected for processing (1)
  • packages/router-core/src/ssr/ssr-client.ts

@codspeed-hq
Copy link

codspeed-hq bot commented Mar 3, 2026

Merging this PR will not alter performance

✅ 5 untouched benchmarks


Comparing sleitor:fix-6806 (f1f10f7) with main (2217f7c)

Open in CodSpeed

@codspeed-hq
Copy link

codspeed-hq bot commented Mar 3, 2026

Merging this PR will not alter performance

✅ 5 untouched benchmarks


Comparing sleitor:fix-6806 (f098bef) with main (2217f7c)

Open in CodSpeed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: Hydration crash in CJS @tanstack/router-core due to fatal invariant in ssr-client.cjs (mismatch with ESM behaviour)

1 participant