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.

@FrancoKaddour
FrancoKaddour marked this pull request as ready for review August 3, 2026 12:30
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