Skip to content

fix(router): address lane loader regressions - #7880

Merged
Sheraff merged 3 commits into
fix-router-core-lane-match-loaderfrom
fix-router-core-lane-match-loader-regressions
Jul 23, 2026
Merged

fix(router): address lane loader regressions#7880
Sheraff merged 3 commits into
fix-router-core-lane-match-loaderfrom
fix-router-core-lane-match-loader-regressions

Conversation

@Sheraff

@Sheraff Sheraff commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Abandoned server chunks: Server route loading can finish after an earlier route chunk fails while later chunk promises are still running. If the request aborts before those chunks settle, they can reject without an owner and trigger unhandled-rejection noise or process failures.
  • Successful undefined loader hydration: Serialization omits an actual value for a loader that successfully returns undefined. Hydration could mistake that for a loader that never ran, losing its successful cache state and selecting or rerunning later failures incorrectly.
  • Selective-SSR head and script parity: During hydration, a data-only or ssr:false boundary could make the client temporarily omit head tags, stylesheets, preloads, or scripts that the server rendered for verified descendant routes. This caused server/client head mismatches and transient tag removal or FOUC until the client load completed.
  • Transactional development HMR: HMR could reuse stale route matches and release currently rendered resources before the replacement render was acknowledged. A failed or superseded refresh could blank or rewind the app, abort live loader signals, strand navigation promises, or fire onEnter instead of onStay.
  • Initial URL canonicalization: The first URL-normalization replacement went through navigation blockers. A blocker could reject that replacement before the initial load, leaving the current URL idle and unrendered.
  • Vue outgoing matches: During route teardown, an outgoing Vue component could briefly observe its own useMatch value become undefined before Vue unmounted it. This exposed impossible transient state to component effects and could trigger invariant failures or undefined property reads.
  • Devtools same-key cache replacement: The devtools watched only cache Map identity and size. Replacing an existing key with a new match object changed neither, so the panel could permanently display stale loader data, status, and timestamps.
  • Scope: This intentionally excludes Solid/Vue Tanstack Router (with Tanstack Query) throws CancelledError when navigating between pages #4476 owner-retention machinery because the original Query CancelledError was not reproduced in those adapters. Avoiding that unproven parity contract also avoids shipping a large branch-retention runtime.

Test plan

  • full router-core, React Router, Solid Router, Vue Router, and router-devtools-core unit suites
  • type and ESLint targets for router-core, React Router, Solid Router, Vue Router, router-devtools-core, and router-plugin
  • router-plugin hot-update regression suite
  • full 17-scenario bundle-size benchmark against the exact base

Bundle size

  • router minimal gzip: React +27 B, Solid +25 B, Vue +67 B
  • router full gzip: React +141 B, Solid +135 B, Vue +142 B
  • Start gzip: React +265-351 B, Solid +273-292 B, Vue +290-339 B
  • production bundles contain no development HMR implementation

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a9f1bf6d-dab0-482c-ac38-2d32a3d6aa41

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-router-core-lane-match-loader-regressions

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

@nx-cloud

nx-cloud Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 0561968

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 5m 19s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 24s View ↗

☁️ Nx Cloud last updated this comment at 2026-07-23 07:56:41 UTC

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

No changeset entries found. Merging this PR will not cause a version bump for any packages.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Benchmarks

  • Commit: ccabeb653337
  • Measured at: 2026-07-23T07:51:56.096Z
  • Baseline source: history:edf55759d8e9
  • Dashboard: bundle-size history

The following scenarios have bundle-size changes compared with the baseline:

Scenario Current (gzip) Delta vs baseline Initial gzip Raw Brotli Trend
react-router.minimal 87.33 KiB -22 B (-0.02%) 87.19 KiB 269.70 KiB 76.05 KiB ███████████▁
react-router.full 91.07 KiB +7 B (+0.01%) 90.93 KiB 281.71 KiB 79.44 KiB ▁▁▁▁▁▁▁▁▁▁▁█
solid-router.minimal 35.07 KiB -471 B (-1.29%) 34.94 KiB 100.48 KiB 31.62 KiB ███████████▁
solid-router.full 40.09 KiB -504 B (-1.21%) 39.97 KiB 115.71 KiB 36.12 KiB ███████████▁
vue-router.minimal 52.28 KiB -739 B (-1.36%) 52.15 KiB 143.65 KiB 47.07 KiB ███████████▁
vue-router.full 58.26 KiB -692 B (-1.15%) 58.13 KiB 162.42 KiB 52.29 KiB ███████████▁
react-start.minimal 102.53 KiB +558 B (+0.53%) 102.39 KiB 318.82 KiB 88.79 KiB ▁▁▁▁▁▁▁▁▁▁▁█
react-start.deferred-hydration 103.26 KiB +555 B (+0.53%) 102.42 KiB 320.19 KiB 89.43 KiB ▁▁▁▁▁▁▁▁▁▁▁█
react-start.full 105.81 KiB +459 B (+0.43%) 105.67 KiB 328.82 KiB 91.65 KiB ▁▁▁▁▁▁▁▁▁▁▁█
react-start.rsbuild.minimal 100.08 KiB +406 B (+0.40%) 99.91 KiB 313.27 KiB 86.30 KiB ▁▁▁▁▁▁▁▁▁▁▁█
react-start.rsbuild.minimal-iife 100.47 KiB +400 B (+0.39%) 100.30 KiB 314.20 KiB 86.62 KiB ▁▁▁▁▁▁▁▁▁▁▁█
react-start.rsbuild.full 103.41 KiB +530 B (+0.50%) 103.24 KiB 323.42 KiB 89.02 KiB ▁▁▁▁▁▁▁▁▁▁▁█
solid-start.minimal 50.04 KiB +439 B (+0.86%) 49.91 KiB 148.48 KiB 44.36 KiB ▁▁▁▁▁▁▁▁▁▁▁█
solid-start.deferred-hydration 53.30 KiB +445 B (+0.82%) 49.97 KiB 156.52 KiB 47.30 KiB ▁▁▁▁▁▁▁▁▁▁▁█
solid-start.full 55.35 KiB -68 B (-0.12%) 55.23 KiB 164.27 KiB 48.89 KiB ███████████▁
vue-start.minimal 70.79 KiB -241 B (-0.33%) 70.66 KiB 201.52 KiB 62.80 KiB ███████████▁
vue-start.full 74.79 KiB -251 B (-0.33%) 74.66 KiB 214.13 KiB 66.32 KiB ███████████▁

Current gzip tracks all emitted client JS chunks. Initial gzip tracks only the entry/import graph. Trend sparkline is historical current gzip ending with this PR measurement; lower is better.

@pkg-pr-new

pkg-pr-new Bot commented Jul 22, 2026

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@7880

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-rsc

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

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

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: 0561968

@codspeed-hq

codspeed-hq Bot commented Jul 22, 2026

Copy link
Copy Markdown

Merging this PR will regress 7 benchmarks

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 11 improved benchmarks
❌ 7 regressed benchmarks
✅ 162 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory mem server error-paths not-found (solid) 409.7 KB 749.8 KB -45.36%
Memory mem server error-paths not-found (vue) 330.5 KB 347.3 KB -4.85%
Memory mem server peak-large-page (solid) 790.6 KB 829.8 KB -4.73%
Simulation ssr global-mw server-route (vue) 68.2 ms 71.4 ms -4.46%
Simulation ssr control-flow route headers (react) 60.3 ms 62.7 ms -3.8%
Simulation client-side navigation loop (vue) 71.2 ms 73.5 ms -3.16%
Simulation client-nested-params navigation loop (vue) 150.3 ms 155.1 ms -3.04%
Memory mem server request-churn (react) 810.6 KB 487.9 KB +66.16%
Memory mem client navigation-churn (solid) 622 KB 516.6 KB +20.41%
Memory mem server error-paths redirect (solid) 362.4 KB 301.7 KB +20.11%
Memory mem server serialization-payload (react) 3.5 MB 3.2 MB +8.35%
Memory mem client unique-location-churn (solid) 527 KB 494.9 KB +6.51%
Memory mem client preload-churn (vue) 779.6 KB 739.5 KB +5.43%
Simulation ssr server-fn not-found (solid) 70.1 ms 66.8 ms +4.96%
Memory mem server error-paths unmatched (react) 270.5 KB 259.7 KB +4.18%
Memory mem server server-fn-churn (vue) 273.8 KB 263.5 KB +3.93%
Memory mem server server-fn-churn (react) 283.6 KB 273.7 KB +3.61%
Simulation ssr control-flow error 500 (react) 62.5 ms 60.7 ms +3%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing fix-router-core-lane-match-loader-regressions (0561968) with fix-router-core-lane-match-loader (f84d087)

Open in CodSpeed

@nx-cloud nx-cloud Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Important

At least one additional CI pipeline execution has run since the conclusion below was written and it may no longer be applicable.

Nx Cloud is proposing a fix for your failed CI:

We add a <Suspense> boundary around the lazy-loaded BaseTanStackRouterDevtoolsPanel in TanStackRouterDevtoolsPanelCore, which fixes the failing cache-replacement unit test. Without it, Solid.js's lazy() throws an unhandled suspension in the jsdom environment and the component never renders, causing vi.waitFor to time out.

Tip

We verified this fix by re-running @tanstack/router-devtools-core:test:unit.

Suggested Fix changes
diff --git a/packages/router-devtools-core/src/TanStackRouterDevtoolsPanelCore.tsx b/packages/router-devtools-core/src/TanStackRouterDevtoolsPanelCore.tsx
index 2d38ac4c..df6a6880 100644
--- a/packages/router-devtools-core/src/TanStackRouterDevtoolsPanelCore.tsx
+++ b/packages/router-devtools-core/src/TanStackRouterDevtoolsPanelCore.tsx
@@ -1,5 +1,5 @@
 import { render } from 'solid-js/web'
-import { createSignal, lazy } from 'solid-js'
+import { Suspense, createSignal, lazy } from 'solid-js'
 import { DevtoolsOnCloseContext, ShadowDomTargetContext } from './context'
 import type { JSX } from 'solid-js'
 import type { AnyRouter } from '@tanstack/router-core'
@@ -97,14 +97,16 @@ export class TanStackRouterDevtoolsPanelCore {
               onCloseClick: () => {},
             }}
           >
-            <BaseTanStackRouterDevtoolsPanel
-              router={router}
-              routerState={routerState}
-              shadowDOMTarget={shadowDOMTarget}
-              setIsOpen={setIsOpen}
-              style={style}
-              className={className}
-            />
+            <Suspense>
+              <BaseTanStackRouterDevtoolsPanel
+                router={router}
+                routerState={routerState}
+                shadowDOMTarget={shadowDOMTarget}
+                setIsOpen={setIsOpen}
+                style={style}
+                className={className}
+              />
+            </Suspense>
           </DevtoolsOnCloseContext.Provider>
         </ShadowDomTargetContext.Provider>
       )

Apply fix via Nx Cloud  Reject fix via Nx Cloud


Or Apply changes locally with:

npx nx-cloud apply-locally 0J3J-ZBUL

Apply fix locally with your editor ↗   View interactive diff ↗



🎓 Learn more about Self-Healing CI on nx.dev

Sheraff and others added 2 commits July 23, 2026 00:30
- rename the internal `_dataOnlyAssetEnd` match field to `_assetEnd`:
  property keys survive minification, so the shorter internal name saves
  bytes at every site in every bundle
- reduce the `_getAssetMatches` guard to a presence check: the field is
  only ever set on hydration presentation clones that already satisfy
  the pending/data-only/error-free/not-not-found conditions, and commits
  clear it (invariant documented at the guard)
- clone-then-patch the hydration handoff splice instead of spreading a
  conditional object per element, and drop the unreachable zero guard in
  `_getAssetMatches`

Measured (gzip, vs PR tip): react-router.full -43 B, vue-router.full
-35 B, react-router.minimal -8 B. All unit suites green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Sheraff
Sheraff merged commit 90a7f39 into fix-router-core-lane-match-loader Jul 23, 2026
25 checks passed
@Sheraff
Sheraff deleted the fix-router-core-lane-match-loader-regressions branch July 23, 2026 07:57
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.

1 participant