Skip to content

fix(router-plugin): don't patch foreign routers on first route-module import - #7937

Open
FrancoKaddour wants to merge 1 commit into
TanStack:mainfrom
FrancoKaddour:fix/7921-hmr-multi-router-first-import
Open

fix(router-plugin): don't patch foreign routers on first route-module import#7937
FrancoKaddour wants to merge 1 commit into
TanStack:mainfrom
FrancoKaddour:fix/7921-hmr-multi-router-first-import

Conversation

@FrancoKaddour

@FrancoKaddour FrancoKaddour commented Aug 2, 2026

Copy link
Copy Markdown

Closes #7921

Problem

Since @tanstack/router-plugin@1.168.17 (837897f, #7560), the injected Vite HMR preamble eagerly calls handleRouteUpdate during the initial evaluation of every route module whenever window.__TSR_ROUTER__ already owns a route with the same id but a different object identity.

That heuristic assumes "same id + different identity ⇒ re-imported copy of this file". With multiple routers in one window (module federation host/remote, microfrontends), route ids like __root__ and / collide between apps, and window.__TSR_ROUTER__ points at whichever router was created last. The remote's route modules then patch the host's router: the host's route options are overwritten, preserveComponentIdentity grafts the host's components onto the remote's route options, and syncHotRouteExport rewrites the remote's Route export (parentRoute, _id, _path, …) with the host's tree state. Since each app ships its own React copy in dev, rendering the remote's router executes components bound to the host's React → "Invalid hook call". Creating the host router is enough to trigger it — no rendering needed.

Fix

Vite's hot.data persists across re-evaluations of a module but starts empty on first import — exactly the missing discriminator. The eager patch (only needed for the #4303 aliased-import re-evaluation case) is now gated behind a tsr-route-initialized flag in hot.data, restoring the pre-1.168.17 behavior for first imports while keeping the #4303 fix intact.

Note: the hot.accept path has always resolved window.__TSR_ROUTER__ (pre-1.168.17), so editing a route file while a foreign router was created last can still target the wrong router. That's a pre-existing, edit-time-only limitation; properly scoping the global to a per-router registry is left as a follow-up if maintainers want it.

Summary by CodeRabbit

  • Bug Fixes

    • Improved Vite hot-module reloading to prevent route conflicts during initial application loads.
    • Prevented routes from separate router instances from being incorrectly merged or updated.
    • Ensured existing routes are only reused during genuine hot re-evaluations.
  • Tests

    • Added coverage for route initialization and hot re-evaluation behavior across supported route patterns and frameworks.

… import

Since 1.168.17 (837897f, TanStack#7560) the injected Vite HMR preamble eagerly calls
handleRouteUpdate during the initial evaluation of every route module whenever
window.__TSR_ROUTER__ already owns a route with the same id but a different
object identity. With multiple routers in one window (module federation
host/remote), route ids like __root__ collide between apps and the last-created
router wins the global, so the remote's route modules patch the host's router
on first import and get the host's tree state written back into their own Route
exports — mixing components across the two React copies and triggering
"Invalid hook call".

hot.data persists across re-evaluations of a module but starts empty on first
import, which is exactly the missing discriminator: the eager patch (only
needed for the TanStack#4303 aliased-import re-evaluation case) is now gated behind a
tsr-route-initialized flag in hot.data, restoring pre-1.168.17 behavior for
first imports while keeping TanStack#4303 fixed.

Closes TanStack#7921
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b6e35740-f615-4f49-80f7-05b24d771220

📥 Commits

Reviewing files that changed from the base of the PR and between 2cb221c and cb44f0c.

📒 Files selected for processing (14)
  • .changeset/fix-multi-router-first-import.md
  • packages/router-plugin/src/core/hmr/vite-adapter.ts
  • packages/router-plugin/tests/add-hmr.test.ts
  • packages/router-plugin/tests/add-hmr/snapshots/react/arrow-function@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/createFileRoute-lowercase-components@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/createRootRoute-inline-component@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/createRootRoute-lowercase-components@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/createRootRoute@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/createRootRouteWithContext-type-args@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/explicit-undefined-component@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/function-declaration@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/multi-component@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/react/string-literal-keys@true.tsx
  • packages/router-plugin/tests/add-hmr/snapshots/solid/arrow-function@true.tsx

📝 Walkthrough

Walkthrough

Changes

HMR route initialization

Layer / File(s) Summary
Gate eager route patching
packages/router-plugin/src/core/hmr/vite-adapter.ts, .changeset/fix-multi-router-first-import.md
The Vite adapter stores tsr-route-initialized in hot data and looks up existing routes only after prior initialization.
Update generated HMR output and coverage
packages/router-plugin/tests/add-hmr.test.ts, packages/router-plugin/tests/add-hmr/snapshots/{react,solid}/*
Tests and snapshots verify initialization tracking and hot-reevaluation gating across route module patterns.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested labels: package: router-plugin

Suggested reviewers: schiller-manuel, sheraff

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the fix that prevents foreign routers from being patched during the first route-module import.
Linked Issues check ✅ Passed The changes gate initial HMR route patching and preserve re-evaluation behavior, directly addressing issue #7921.
Out of Scope Changes check ✅ Passed All changes support the router-plugin fix, including implementation, tests, snapshots, and the release changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

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

Labels

None yet

Projects

None yet

1 participant