Skip to content

Conversation

@schiller-manuel
Copy link
Contributor

@schiller-manuel schiller-manuel commented Oct 31, 2025

fixes #5698

Summary by CodeRabbit

  • Bug Fixes

    • Resolved duplicate Hot Module Replacement (HMR) code insertions during route updates in development environments
  • Refactor

    • Updated route state synchronization approach during hot module replacement to enhance consistency and prevent redundant code insertions
    • Removed deprecated cloning mechanism for route instances

@nx-cloud
Copy link

nx-cloud bot commented Oct 31, 2025

View your CI Pipeline Execution ↗ for commit d35c4c3

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

☁️ Nx Cloud last updated this comment at 2025-10-31 19:32:32 UTC

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 31, 2025

Walkthrough

The changes remove the clone method from BaseRoute and implement a new HMR update mechanism via an inline handleRouteUpdate helper that directly copies private route properties, updates router caches, and invalidates affected routes. HMR code-splitting logic is guarded with an hmrAdded flag to prevent duplicate insertions.

Changes

Cohort / File(s) Change Summary
Route Core Removal
packages/router-core/src/route.ts
Removed clone method from BaseRoute class that previously copied internal state (_path, _id, _fullPath, _to, children, parentRoute) between route instances.
HMR Compilation Safety
packages/router-plugin/src/core/code-splitter/compilers.ts
Added hmrAdded flag to track HMR insertion state and guard all code-splitting HMR code appends to prevent duplicate routeHmrStatement insertions across path handling and lazy-route branches.
HMR Update Mechanism
packages/router-plugin/src/core/route-hmr-statement.ts
Introduced handleRouteUpdate helper and private-augmented route type to replace Route.clone pattern. New helper copies private fields, updates router caches (routesById, routesByPath, flatRoutes), and triggers router.invalidate with old route ID filter. Added placeholderPattern: false template option for identifier preservation.
Test Snapshots (React & Solid)
packages/router-plugin/tests/add-hmr/snapshots/react/[email protected], [email protected], solid/[email protected]
Updated HMR snapshot outputs to reflect new handleRouteUpdate pattern instead of Route.clone invocation, with inline route property copying and cache updates.

Sequence Diagram(s)

sequenceDiagram
    participant Vite as Vite HMR
    participant OldModule as Old Route Module
    participant NewModule as New Route Module
    participant Router as Router Instance
    participant Caches as Router Caches
    
    Vite->>NewModule: Hot update received
    NewModule->>Router: Check Route & newModule.Route
    alt Route exists
        NewModule->>NewModule: handleRouteUpdate(oldRoute, newRoute)
        Note over NewModule: Copy private fields<br/>(_path, _id, _fullPath, _to, etc)
        NewModule->>Caches: Update routesById[id]
        NewModule->>Caches: Update routesByPath[path]
        NewModule->>Caches: Replace in flatRoutes
        NewModule->>Router: invalidate(filter: oldRouteId)
        Router->>Router: Re-evaluate route tree
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Logic coordination: The handleRouteUpdate helper replaces a removed method, requiring careful validation that all private fields are correctly copied and router caches stay synchronized.
  • HMR flow validation: Verify hmrAdded flag logic prevents duplicate insertions across all code-splitting paths (non-splittable, lazy-route, and component branches).
  • Snapshot consistency: Four snapshot files need coordinated review to ensure the new pattern is correctly applied across different route declaration styles (arrow functions, function declarations, frameworks).
  • Placeholder parsing change: The placeholderPattern: false template option warrants verification it doesn't affect identifier parsing in existing use cases.

Poem

🐰 No more cloning routes in the night,
A fresh update flow, burning bright!
HMR guards prevent the duplicate sin,
Cache updates spin, let changes in!
Hot reloads dance without the clone,
Router state's now synchronous-grown! 🚀

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "fix: route HMR handling" accurately describes the main change in the pull request. The changeset focuses on improving Hot Module Replacement (HMR) handling for routes by removing the clone() method and replacing it with a new handleRouteUpdate mechanism that properly mutates route state and invalidates the router. The title is concise, specific, and clearly conveys the primary fix being implemented.
Linked Issues Check ✅ Passed The PR successfully addresses the requirements of issue #5698, which requires HMR to automatically re-run updated beforeLoad and loader code without requiring a full page refresh. The implementation replaces the previous Route.clone() approach with a new handleRouteUpdate helper that copies private route properties, updates router caches (routesById, routesByPath, flatRoutes), and critically invokes router.invalidate() to trigger proper re-evaluation [issue #5698]. Additionally, the hmrAdded flag prevents duplicate HMR insertions to ensure consistent control flow, and placeholderPattern: false addresses template parsing for proper identifier handling during HMR. These changes directly fix the HMR issue by ensuring route updates are properly propagated and the router invalidates affected routes.
Out of Scope Changes Check ✅ Passed All changes in the pull request are directly related to fixing HMR handling for routes. The removal of the clone() method, introduction of handleRouteUpdate helper, addition of the hmrAdded flag to prevent duplicate HMR code, and updates to HMR statements all support the primary objective of fixing HMR to properly refresh routes. The test snapshot updates reflect the new HMR mechanism, and the placeholderPattern configuration change ensures proper template parsing during HMR. No out-of-scope modifications were detected that fall outside the scope of fixing HMR behavior for route updates.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-5698

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

@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 31, 2025

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@5710

@tanstack/directive-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/directive-functions-plugin@5710

@tanstack/eslint-plugin-router

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

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@5710

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@5710

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@5710

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@5710

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/server-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/server-functions-plugin@5710

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@5710

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@5710

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@5710

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@5710

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@5710

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@5710

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-storage-context@5710

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@5710

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@5710

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@5710

commit: d35c4c3

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 943d616 and d35c4c3.

📒 Files selected for processing (6)
  • packages/router-core/src/route.ts (0 hunks)
  • packages/router-plugin/src/core/code-splitter/compilers.ts (3 hunks)
  • packages/router-plugin/src/core/route-hmr-statement.ts (1 hunks)
  • packages/router-plugin/tests/add-hmr/snapshots/react/[email protected] (1 hunks)
  • packages/router-plugin/tests/add-hmr/snapshots/react/[email protected] (1 hunks)
  • packages/router-plugin/tests/add-hmr/snapshots/solid/[email protected] (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/router-core/src/route.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript in strict mode with extensive type safety across the codebase

Files:

  • packages/router-plugin/tests/add-hmr/snapshots/solid/[email protected]
  • packages/router-plugin/src/core/code-splitter/compilers.ts
  • packages/router-plugin/tests/add-hmr/snapshots/react/[email protected]
  • packages/router-plugin/tests/add-hmr/snapshots/react/[email protected]
  • packages/router-plugin/src/core/route-hmr-statement.ts
packages/{router-cli,router-generator,router-plugin,virtual-file-routes}/**

📄 CodeRabbit inference engine (AGENTS.md)

Keep CLI, generators, bundler plugins, and virtual file routing utilities in their dedicated tooling package directories

Files:

  • packages/router-plugin/tests/add-hmr/snapshots/solid/[email protected]
  • packages/router-plugin/src/core/code-splitter/compilers.ts
  • packages/router-plugin/tests/add-hmr/snapshots/react/[email protected]
  • packages/router-plugin/tests/add-hmr/snapshots/react/[email protected]
  • packages/router-plugin/src/core/route-hmr-statement.ts
packages/router-plugin/**

📄 CodeRabbit inference engine (AGENTS.md)

Use unplugin for universal bundler plugins in the router-plugin package

Files:

  • packages/router-plugin/tests/add-hmr/snapshots/solid/[email protected]
  • packages/router-plugin/src/core/code-splitter/compilers.ts
  • packages/router-plugin/tests/add-hmr/snapshots/react/[email protected]
  • packages/router-plugin/tests/add-hmr/snapshots/react/[email protected]
  • packages/router-plugin/src/core/route-hmr-statement.ts
🧠 Learnings (5)
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • packages/router-plugin/tests/add-hmr/snapshots/solid/[email protected]
  • packages/router-plugin/src/core/code-splitter/compilers.ts
  • packages/router-plugin/tests/add-hmr/snapshots/react/[email protected]
  • packages/router-plugin/tests/add-hmr/snapshots/react/[email protected]
  • packages/router-plugin/src/core/route-hmr-statement.ts
📚 Learning: 2025-09-23T17:36:12.598Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-23T17:36:12.598Z
Learning: Applies to packages/{react-router,solid-router}/** : Implement React and Solid bindings/components only in packages/react-router/ and packages/solid-router/

Applied to files:

  • packages/router-plugin/tests/add-hmr/snapshots/solid/[email protected]
  • packages/router-plugin/tests/add-hmr/snapshots/react/[email protected]
  • packages/router-plugin/tests/add-hmr/snapshots/react/[email protected]
  • packages/router-plugin/src/core/route-hmr-statement.ts
📚 Learning: 2025-10-01T18:30:26.591Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: packages/router-core/src/router.ts:2231-2245
Timestamp: 2025-10-01T18:30:26.591Z
Learning: In `packages/router-core/src/router.ts`, the `resolveRedirect` method intentionally strips the router's origin from redirect URLs when they match (e.g., `https://foo.com/bar` → `/bar` for same-origin redirects) while preserving the full URL for cross-origin redirects. This logic should not be removed or simplified to use `location.publicHref` directly.

Applied to files:

  • packages/router-plugin/tests/add-hmr/snapshots/solid/[email protected]
  • packages/router-plugin/src/core/route-hmr-statement.ts
📚 Learning: 2025-09-23T17:36:12.598Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-23T17:36:12.598Z
Learning: Applies to packages/router-core/** : Keep framework-agnostic core router logic in packages/router-core/

Applied to files:

  • packages/router-plugin/tests/add-hmr/snapshots/solid/[email protected]
  • packages/router-plugin/tests/add-hmr/snapshots/react/[email protected]
  • packages/router-plugin/tests/add-hmr/snapshots/react/[email protected]
  • packages/router-plugin/src/core/route-hmr-statement.ts
📚 Learning: 2025-09-23T17:36:12.598Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-09-23T17:36:12.598Z
Learning: Applies to packages/router-plugin/** : Use unplugin for universal bundler plugins in the router-plugin package

Applied to files:

🧬 Code graph analysis (5)
packages/router-plugin/tests/add-hmr/snapshots/solid/[email protected] (1)
packages/router-core/src/route.ts (1)
  • Route (586-776)
packages/router-plugin/src/core/code-splitter/compilers.ts (1)
packages/router-plugin/src/core/route-hmr-statement.ts (1)
  • routeHmrStatement (32-44)
packages/router-plugin/tests/add-hmr/snapshots/react/[email protected] (1)
packages/router-core/src/route.ts (1)
  • Route (586-776)
packages/router-plugin/tests/add-hmr/snapshots/react/[email protected] (1)
packages/router-core/src/route.ts (1)
  • Route (586-776)
packages/router-plugin/src/core/route-hmr-statement.ts (1)
packages/router-core/src/route.ts (1)
  • AnyRoute (778-797)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test

Comment on lines +19 to +30
newRoute.children = oldRoute.children
newRoute.parentRoute = oldRoute.parentRoute

const router = window.__TSR_ROUTER__!
router.routesById[newRoute.id] = newRoute
router.routesByPath[newRoute.fullPath] = newRoute
const oldRouteIndex = router.flatRoutes.indexOf(oldRoute)
if (oldRouteIndex > -1) {
router.flatRoutes[oldRouteIndex] = newRoute
}
router.invalidate({ filter: (m) => m.routeId === oldRoute.id })
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Update parent/child bindings when swapping the route instance.

We copy private fields and replace the cache maps, but the parent route’s children array still holds oldRoute, and every child keeps its parentRoute pointing to the stale instance. Active matches and tree traversal walk those references, so beforeLoad/loader calls continue to use the pre-update implementation—the bug this PR is targeting. Please swap the instance in the parent’s children array and retarget each child before calling invalidate.

   newRoute.parentRoute = oldRoute.parentRoute
+
+  const parentChildren = newRoute.parentRoute?.children as
+    | Array<AnyRouteWithPrivateProps>
+    | undefined
+  if (parentChildren) {
+    const parentIndex = parentChildren.indexOf(oldRoute as AnyRouteWithPrivateProps)
+    if (parentIndex > -1) {
+      parentChildren[parentIndex] = newRoute
+    }
+  }
+
+  const childRoutes = Array.isArray(newRoute.children)
+    ? (newRoute.children as Array<AnyRouteWithPrivateProps>)
+    : undefined
+  childRoutes?.forEach((child) => {
+    child.parentRoute = newRoute
+  })
 
   const router = window.__TSR_ROUTER__!
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
newRoute.children = oldRoute.children
newRoute.parentRoute = oldRoute.parentRoute
const router = window.__TSR_ROUTER__!
router.routesById[newRoute.id] = newRoute
router.routesByPath[newRoute.fullPath] = newRoute
const oldRouteIndex = router.flatRoutes.indexOf(oldRoute)
if (oldRouteIndex > -1) {
router.flatRoutes[oldRouteIndex] = newRoute
}
router.invalidate({ filter: (m) => m.routeId === oldRoute.id })
}
newRoute.children = oldRoute.children
newRoute.parentRoute = oldRoute.parentRoute
const parentChildren = newRoute.parentRoute?.children as
| Array<AnyRouteWithPrivateProps>
| undefined
if (parentChildren) {
const parentIndex = parentChildren.indexOf(oldRoute as AnyRouteWithPrivateProps)
if (parentIndex > -1) {
parentChildren[parentIndex] = newRoute
}
}
const childRoutes = Array.isArray(newRoute.children)
? (newRoute.children as Array<AnyRouteWithPrivateProps>)
: undefined
childRoutes?.forEach((child) => {
child.parentRoute = newRoute
})
const router = window.__TSR_ROUTER__!
router.routesById[newRoute.id] = newRoute
router.routesByPath[newRoute.fullPath] = newRoute
const oldRouteIndex = router.flatRoutes.indexOf(oldRoute)
if (oldRouteIndex > -1) {
router.flatRoutes[oldRouteIndex] = newRoute
}
router.invalidate({ filter: (m) => m.routeId === oldRoute.id })
}
🤖 Prompt for AI Agents
In packages/router-plugin/src/core/route-hmr-statement.ts around lines 19–30,
after copying newRoute.children = oldRoute.children and newRoute.parentRoute =
oldRoute.parentRoute, swap the instance in the parent’s children array (if
parentRoute exists) by finding oldRoute in parent.children and replacing it with
newRoute, then iterate newRoute.children and set each child.parentRoute =
newRoute so children point at the new instance; do these updates before calling
router.invalidate so active matches and tree traversal use the new route
instance.

@schiller-manuel schiller-manuel merged commit 93d19ad into main Oct 31, 2025
6 checks passed
@schiller-manuel schiller-manuel deleted the fix-5698 branch October 31, 2025 19:38
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.

Start: HMR does not refresh loaders or beforeLoad

2 participants