Repository: TanStack/router
Pull request #1721 (2024-06-06 21:33:27)

fix(react-router): take `from` into account when navigating with `params` or layouts
- Use flatRoutes so we can select branches even if they have index routes
- Do not override `from` redirect preloads. Users should specify this manually
- Ignore `from` in declarative route masks when building the location
- Add tests to cover bugs encountered

nx-cloud[bot]: # ☁️ Nx Cloud Report

CI is running for commit 9748ac9c7902a39483a03985fb635330a4f4f373.

[📂 Click to track the progress, see the status, the terminal output, and the build insights.](https://cloud.nx.app/cipes/66622b48fb265e28c4bf3c07?utm_source=pull-request&utm_medium=comment)


---

Sent with 💌 from [NxCloud.](https://nx.app?utm_source=pull-request&utm_medium=comment)

Patch:
diff --git a/docs/framework/react/guide/authenticated-routes.md b/docs/framework/react/guide/authenticated-routes.md
index 6a3228899..cac200b0b 100644
--- a/docs/framework/react/guide/authenticated-routes.md
+++ b/docs/framework/react/guide/authenticated-routes.md
@@ -61,7 +61,7 @@ router.history.push(search.redirect)
 Some applications choose to not redirect users to a login page, and instead keep the user on the same page and show a login form that either replaces the main content or hides it via a modal. This is also possible with TanStack Router by simply short circuiting rendering the `<Outlet />` that would normally render the child routes:
 
 ```tsx
-// src/routes/authenticated.tsx
+// src/routes/_authenticated.tsx
 export const Route = createFileRoute('/_authenticated')({
   component: () => {
     if (!isAuthenticated()) {
diff --git a/docs/framework/react/guide/route-masking.md b/docs/framework/react/guide/route-masking.md
index d120295c7..15ff6b3c5 100644
--- a/docs/framework/react/guide/route-masking.md
+++ b/docs/framework/react/guide/route-masking.md
@@ -2,12 +2,12 @@
 title: Route Masking
 ---
 
-Route masking is a way to mask the actual URL of a route that gets persisted to the browser's history and URL bar. This is useful for scenarios where you want to show a different URL than the one that is actually being navigated to and then falling back to the displayed URL when it is shared and (optionally) when the the page is reloaded. Here's a few examples:
+Route masking is a way to mask the actual URL of a route that gets persisted to the browser's history and URL bar. This is useful for scenarios where you want to show a different URL than the one that is actually being navigated to and then falling back to the displayed URL when it is shared and (optionally) when the page is reloaded. Here's a few examples:
 
 - Navigating to a modal route like `/photo/5/modal`, but masking the actual URL as `/photos/5`
 - Navigating to a modal route like `/post/5/comments`, but masking the actual URL as `/posts/5`
-- Navigating to a route with the search param `?showLogin=true`, but masking the the URL to _not_ contain the search param
-- Navigating to a route with the search param `?modal=settings`, but masking the the URL as `/settings'
+- Navigating to a route with the search param `?showLogin=true`, but masking the URL to _not_ contain the search param
+- Navigating to a route with the search param `?modal=settings`, but masking the URL as `/settings'
 
 Each of these scenarios can be achieved with route masking and even extended to support more advanced patterns like [parallel routes](../parallel-routes)
 
diff --git a/examples/react/authenticated-routes/package.json b/examples/react/authenticated-routes/package.json
index 56bc45410..90aaba631 100644
--- a/examples/react/authenticated-routes/package.json
+++ b/examples/react/authenticated-routes/package.json
@@ -9,9 +9,9 @@
     "start": "vite"
   },
   "dependencies": {
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
-    "@tanstack/router-vite-plugin": "^1.34.8",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
+    "@tanstack/router-vite-plugin": "^1.35.4",
     "redaxios": "^0.5.0",
     "immer": "^10.0.3",
     "react": "^18.2.0",
diff --git a/examples/react/basic-default-search-params/package.json b/examples/react/basic-default-search-params/package.json
index 279c648c7..f2fc5c84e 100644
--- a/examples/react/basic-default-search-params/package.json
+++ b/examples/react/basic-default-search-params/package.json
@@ -10,8 +10,8 @@
   },
   "dependencies": {
     "@tanstack/react-query": "^5.17.8",
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
     "redaxios": "^0.5.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
diff --git a/examples/react/basic-file-based-codesplitting/package.json b/examples/react/basic-file-based-codesplitting/package.json
index ab2f578bb..19e2a8afa 100644
--- a/examples/react/basic-file-based-codesplitting/package.json
+++ b/examples/react/basic-file-based-codesplitting/package.json
@@ -9,9 +9,9 @@
     "start": "vite"
   },
   "dependencies": {
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
-    "@tanstack/router-vite-plugin": "^1.34.8",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
+    "@tanstack/router-vite-plugin": "^1.35.4",
     "redaxios": "^0.5.0",
     "immer": "^10.0.3",
     "react": "^18.2.0",
diff --git a/examples/react/basic-file-based/package.json b/examples/react/basic-file-based/package.json
index 42f838c2d..ed0d07e1a 100644
--- a/examples/react/basic-file-based/package.json
+++ b/examples/react/basic-file-based/package.json
@@ -9,9 +9,9 @@
     "start": "vite"
   },
   "dependencies": {
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
-    "@tanstack/router-vite-plugin": "^1.34.8",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
+    "@tanstack/router-vite-plugin": "^1.35.4",
     "redaxios": "^0.5.0",
     "immer": "^10.0.3",
     "react": "^18.2.0",
diff --git a/examples/react/basic-react-query-file-based/package.json b/examples/react/basic-react-query-file-based/package.json
index c32228649..a292f7c6a 100644
--- a/examples/react/basic-react-query-file-based/package.json
+++ b/examples/react/basic-react-query-file-based/package.json
@@ -11,9 +11,9 @@
   "dependencies": {
     "@tanstack/react-query": "^5.17.8",
     "@tanstack/react-query-devtools": "^5.17.8",
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
-    "@tanstack/router-vite-plugin": "^1.34.8",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
+    "@tanstack/router-vite-plugin": "^1.35.4",
     "redaxios": "^0.5.0",
     "immer": "^10.0.3",
     "react": "^18.2.0",
diff --git a/examples/react/basic-react-query/package.json b/examples/react/basic-react-query/package.json
index c9479c730..17bffde60 100644
--- a/examples/react/basic-react-query/package.json
+++ b/examples/react/basic-react-query/package.json
@@ -11,8 +11,8 @@
   "dependencies": {
     "@tanstack/react-query": "^5.17.8",
     "@tanstack/react-query-devtools": "^5.17.8",
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
     "redaxios": "^0.5.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0"
diff --git a/examples/react/basic-ssr-file-based/package.json b/examples/react/basic-ssr-file-based/package.json
index aa9c47142..d245685fd 100644
--- a/examples/react/basic-ssr-file-based/package.json
+++ b/examples/react/basic-ssr-file-based/package.json
@@ -12,10 +12,10 @@
     "debug": "node --inspect-brk server"
   },
   "dependencies": {
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/start": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
-    "@tanstack/router-vite-plugin": "^1.34.8",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/start": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
+    "@tanstack/router-vite-plugin": "^1.35.4",
     "redaxios": "^0.5.0",
     "get-port": "^7.0.0",
     "react": "^18.2.0",
diff --git a/examples/react/basic-ssr-streaming-file-based/package.json b/examples/react/basic-ssr-streaming-file-based/package.json
index d9de0c460..08e257bec 100644
--- a/examples/react/basic-ssr-streaming-file-based/package.json
+++ b/examples/react/basic-ssr-streaming-file-based/package.json
@@ -12,10 +12,10 @@
     "debug": "node --inspect-brk server"
   },
   "dependencies": {
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/start": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
-    "@tanstack/router-vite-plugin": "^1.34.8",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/start": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
+    "@tanstack/router-vite-plugin": "^1.35.4",
     "redaxios": "^0.5.0",
     "get-port": "^7.0.0",
     "react": "^18.2.0",
diff --git a/examples/react/basic/package.json b/examples/react/basic/package.json
index 100970c0d..b24e643c1 100644
--- a/examples/react/basic/package.json
+++ b/examples/react/basic/package.json
@@ -10,8 +10,8 @@
   },
   "dependencies": {
     "@tanstack/react-query": "^5.17.8",
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
     "redaxios": "^0.5.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0"
diff --git a/examples/react/deferred-data/package.json b/examples/react/deferred-data/package.json
index 0f32e0df0..94de2c304 100644
--- a/examples/react/deferred-data/package.json
+++ b/examples/react/deferred-data/package.json
@@ -9,8 +9,8 @@
     "start": "vite"
   },
   "dependencies": {
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
     "redaxios": "^0.5.0",
     "immer": "^10.0.3",
     "react": "^18.2.0",
diff --git a/examples/react/kitchen-sink-file-based/package.json b/examples/react/kitchen-sink-file-based/package.json
index 1de73606f..568151419 100644
--- a/examples/react/kitchen-sink-file-based/package.json
+++ b/examples/react/kitchen-sink-file-based/package.json
@@ -9,9 +9,9 @@
     "start": "vite"
   },
   "dependencies": {
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
-    "@tanstack/router-vite-plugin": "^1.34.8",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
+    "@tanstack/router-vite-plugin": "^1.35.4",
     "redaxios": "^0.5.0",
     "immer": "^10.0.3",
     "react": "^18.2.0",
diff --git a/examples/react/kitchen-sink-react-query-file-based/package.json b/examples/react/kitchen-sink-react-query-file-based/package.json
index 2afcd8d3f..363572930 100644
--- a/examples/react/kitchen-sink-react-query-file-based/package.json
+++ b/examples/react/kitchen-sink-react-query-file-based/package.json
@@ -11,9 +11,9 @@
   "dependencies": {
     "@tanstack/react-query": "^5.17.8",
     "@tanstack/react-query-devtools": "^5.17.8",
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
-    "@tanstack/router-vite-plugin": "^1.34.8",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
+    "@tanstack/router-vite-plugin": "^1.35.4",
     "redaxios": "^0.5.0",
     "immer": "^10.0.3",
     "react": "^18.2.0",
diff --git a/examples/react/kitchen-sink-react-query/package.json b/examples/react/kitchen-sink-react-query/package.json
index d0ad24ddf..8c808ea4d 100644
--- a/examples/react/kitchen-sink-react-query/package.json
+++ b/examples/react/kitchen-sink-react-query/package.json
@@ -11,8 +11,8 @@
   "dependencies": {
     "@tanstack/react-query": "^5.17.8",
     "@tanstack/react-query-devtools": "^5.17.8",
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
     "redaxios": "^0.5.0",
     "immer": "^10.0.3",
     "react": "^18.2.0",
diff --git a/examples/react/kitchen-sink/package.json b/examples/react/kitchen-sink/package.json
index a5b5bc39f..409815ebe 100644
--- a/examples/react/kitchen-sink/package.json
+++ b/examples/react/kitchen-sink/package.json
@@ -9,8 +9,8 @@
     "start": "vite"
   },
   "dependencies": {
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
     "redaxios": "^0.5.0",
     "immer": "^10.0.3",
     "react": "^18.2.0",
diff --git a/examples/react/large-file-based/package.json b/examples/react/large-file-based/package.json
index ccc26e49f..a66260b69 100644
--- a/examples/react/large-file-based/package.json
+++ b/examples/react/large-file-based/package.json
@@ -12,9 +12,9 @@
   },
   "dependencies": {
     "@tanstack/react-query": "^5.17.8",
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
-    "@tanstack/router-vite-plugin": "^1.34.8",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
+    "@tanstack/router-vite-plugin": "^1.35.4",
     "redaxios": "^0.5.0",
     "immer": "^10.0.3",
     "react": "^18.2.0",
diff --git a/examples/react/location-masking/package.json b/examples/react/location-masking/package.json
index 455d8fc3e..bb469d2c1 100644
--- a/examples/react/location-masking/package.json
+++ b/examples/react/location-masking/package.json
@@ -11,8 +11,8 @@
   "dependencies": {
     "@radix-ui/react-dialog": "^1.0.5",
     "@tanstack/react-query": "^5.17.8",
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
     "redaxios": "^0.5.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0"
diff --git a/examples/react/navigation-blocking/package.json b/examples/react/navigation-blocking/package.json
index c10d4cd3b..f629ba0c3 100644
--- a/examples/react/navigation-blocking/package.json
+++ b/examples/react/navigation-blocking/package.json
@@ -10,8 +10,8 @@
   },
   "dependencies": {
     "@tanstack/react-query": "^5.17.8",
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
     "redaxios": "^0.5.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0"
diff --git a/examples/react/quickstart-file-based/package.json b/examples/react/quickstart-file-based/package.json
index 56fc0294a..891a55fbc 100644
--- a/examples/react/quickstart-file-based/package.json
+++ b/examples/react/quickstart-file-based/package.json
@@ -9,9 +9,9 @@
     "start": "vite"
   },
   "dependencies": {
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
-    "@tanstack/router-vite-plugin": "^1.34.8",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
+    "@tanstack/router-vite-plugin": "^1.35.4",
     "redaxios": "^0.5.0",
     "immer": "^10.0.3",
     "react": "^18.2.0",
diff --git a/examples/react/quickstart/package.json b/examples/react/quickstart/package.json
index d41677e7d..f74920aa0 100644
--- a/examples/react/quickstart/package.json
+++ b/examples/react/quickstart/package.json
@@ -9,8 +9,8 @@
     "start": "vite"
   },
   "dependencies": {
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
     "react": "^18.2.0",
     "react-dom": "^18.2.0"
   },
diff --git a/examples/react/scroll-restoration/package.json b/examples/react/scroll-restoration/package.json
index bac0985d8..880ba2389 100644
--- a/examples/react/scroll-restoration/package.json
+++ b/examples/react/scroll-restoration/package.json
@@ -9,9 +9,9 @@
     "start": "vite"
   },
   "dependencies": {
-    "@tanstack/react-router": "^1.35.3",
+    "@tanstack/react-router": "^1.35.5",
     "@tanstack/react-virtual": "^3.0.1",
-    "@tanstack/router-devtools": "^1.35.3",
+    "@tanstack/router-devtools": "^1.35.5",
     "react": "^18.2.0",
     "react-dom": "^18.2.0"
   },
diff --git a/examples/react/start-basic-rsc/package.json b/examples/react/start-basic-rsc/package.json
index 46c6f76a6..5a909cdab 100644
--- a/examples/react/start-basic-rsc/package.json
+++ b/examples/react/start-basic-rsc/package.json
@@ -12,10 +12,10 @@
     "format": "prettier --write '**/*' --ignore-unknown"
   },
   "dependencies": {
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
-    "@tanstack/router-vite-plugin": "^1.34.8",
-    "@tanstack/start": "^1.35.3",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
+    "@tanstack/router-vite-plugin": "^1.35.4",
+    "@tanstack/start": "^1.35.5",
     "redaxios": "^0.5.0",
     "isbot": "^3.7.0",
     "react": "^18.3.1",
diff --git a/examples/react/start-basic/package.json b/examples/react/start-basic/package.json
index ff13b30e8..385373ba6 100644
--- a/examples/react/start-basic/package.json
+++ b/examples/react/start-basic/package.json
@@ -14,10 +14,10 @@
     "test:e2e:replay": "playwright test --project=replay-chromium"
   },
   "dependencies": {
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
-    "@tanstack/router-vite-plugin": "^1.34.8",
-    "@tanstack/start": "^1.35.3",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
+    "@tanstack/router-vite-plugin": "^1.35.4",
+    "@tanstack/start": "^1.35.5",
     "@typescript-eslint/parser": "^7.2.0",
     "@vitejs/plugin-react": "^4",
     "dotenv": "^16.4.5",
diff --git a/examples/react/start-trellaux/package.json b/examples/react/start-trellaux/package.json
index e3a39e5e7..b5b051cf3 100644
--- a/examples/react/start-trellaux/package.json
+++ b/examples/react/start-trellaux/package.json
@@ -14,10 +14,10 @@
   "dependencies": {
     "@tanstack/react-query": "^5.40.0",
     "@tanstack/react-query-devtools": "^5.40.0",
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
-    "@tanstack/router-vite-plugin": "^1.34.1",
-    "@tanstack/start": "^1.35.3",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
+    "@tanstack/router-vite-plugin": "^1.35.4",
+    "@tanstack/start": "^1.35.5",
     "@typescript-eslint/parser": "^7.2.0",
     "@vitejs/plugin-react": "^4",
     "isbot": "^3.7.0",
diff --git a/examples/react/wip-trpc-react-query/package.json b/examples/react/wip-trpc-react-query/package.json
index 17ce660a6..f10e7b3c4 100644
--- a/examples/react/wip-trpc-react-query/package.json
+++ b/examples/react/wip-trpc-react-query/package.json
@@ -13,8 +13,8 @@
   "dependencies": {
     "@tanstack/react-query": "^4.36.1",
     "@tanstack/react-query-devtools": "^4.36.1",
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
     "@trpc/client": "^10.45.0",
     "@trpc/react-query": "^10.45.0",
     "@trpc/server": "^10.45.0",
diff --git a/examples/react/wip-waku-rsc/package.json b/examples/react/wip-waku-rsc/package.json
index 41ead0c16..5a99b5de3 100644
--- a/examples/react/wip-waku-rsc/package.json
+++ b/examples/react/wip-waku-rsc/package.json
@@ -10,8 +10,8 @@
   },
   "dependencies": {
     "@hono/node-server": "^1.4.1",
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
     "hono": "^3.12.0",
     "react": "18.3.0-canary-0cdfef19b-20231211",
     "react-dom": "18.3.0-canary-0cdfef19b-20231211",
diff --git a/examples/react/wip-with-bling/package.json b/examples/react/wip-with-bling/package.json
index b29fd4d82..cf24ecd5b 100644
--- a/examples/react/wip-with-bling/package.json
+++ b/examples/react/wip-with-bling/package.json
@@ -13,10 +13,10 @@
   },
   "dependencies": {
     "@tanstack/bling": "^0.5.0",
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/start": "^1.35.3",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/start": "^1.35.5",
     "@tanstack/react-store": "^0.2.1",
-    "@tanstack/router-devtools": "^1.35.3",
+    "@tanstack/router-devtools": "^1.35.5",
     "redaxios": "^0.5.0",
     "fastify": "^4.25.2",
     "get-port": "^7.0.0",
diff --git a/examples/react/wip-with-framer-motion/package.json b/examples/react/wip-with-framer-motion/package.json
index 10dd297de..fe6b00f1e 100644
--- a/examples/react/wip-with-framer-motion/package.json
+++ b/examples/react/wip-with-framer-motion/package.json
@@ -9,8 +9,8 @@
     "start": "vite"
   },
   "dependencies": {
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
     "redaxios": "^0.5.0",
     "framer-motion": "^10.17.9",
     "immer": "^10.0.3",
diff --git a/examples/react/with-trpc/package.json b/examples/react/with-trpc/package.json
index 0b57d9d09..84a65569c 100644
--- a/examples/react/with-trpc/package.json
+++ b/examples/react/with-trpc/package.json
@@ -13,8 +13,8 @@
   "dependencies": {
     "@tanstack/react-query": "^4.36.1",
     "@tanstack/react-query-devtools": "^4.36.1",
-    "@tanstack/react-router": "^1.35.3",
-    "@tanstack/router-devtools": "^1.35.3",
+    "@tanstack/react-router": "^1.35.5",
+    "@tanstack/router-devtools": "^1.35.5",
     "@trpc/client": "^10.45.0",
     "@trpc/react-query": "^10.45.0",
     "@trpc/server": "^10.45.0",
diff --git a/packages/react-router/package.json b/packages/react-router/package.json
index 92bb9f269..db00d85c3 100644
--- a/packages/react-router/package.json
+++ b/packages/react-router/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@tanstack/react-router",
-  "version": "1.35.3",
+  "version": "1.35.5",
   "description": "",
   "author": "Tanner Linsley",
   "license": "MIT",
diff --git a/packages/react-router/src/index.tsx b/packages/react-router/src/index.tsx
index 47f151341..663e11ce4 100644
--- a/packages/react-router/src/index.tsx
+++ b/packages/react-router/src/index.tsx
@@ -156,7 +156,6 @@ export {
   type AnyRoute,
   type RouteConstraints,
   type AnyRootRoute,
-  type RootSearchSchema,
   type ResolveFullPath,
   type RouteMask,
   type ErrorRouteProps,
diff --git a/packages/react-router/src/link.tsx b/packages/react-router/src/link.tsx
index 0783dce0a..80a3c0230 100644
--- a/packages/react-router/src/link.tsx
+++ b/packages/react-router/src/link.tsx
@@ -7,7 +7,6 @@ import { deepEqual, functionalUpdate } from './utils'
 import { exactPathTest, removeTrailingSlash } from './path'
 import type { AnyRouter, ParsedLocation } from '.'
 import type { HistoryState } from '@tanstack/history'
-import type { AnyRoute, RootSearchSchema } from './route'
 import type {
   AllParams,
   CatchAllPaths,
@@ -251,9 +250,6 @@ type ParamsReducer<
 
 type ParamVariant = 'PATH' | 'SEARCH'
 
-type ExcludeRootSearchSchema<T> =
-  Exclude<T, RootSearchSchema> extends never ? {} : Exclude<T, RootSearchSchema>
-
 export type ResolveRoute<
   TRouter extends AnyRouter,
   TFrom,
@@ -265,11 +261,6 @@ export type ResolveRoute<
     : RouteByToPath<TRouter, TPath>
   : never
 
-type PostProcessParams<
-  T,
-  TParamVariant extends ParamVariant,
-> = TParamVariant extends 'SEARCH' ? ExcludeRootSearchSchema<T> : T
-
 type ResolveFromParamType<TParamVariant extends ParamVariant> =
   TParamVariant extends 'PATH' ? 'allParams' : 'fullSearchSchema'
 
@@ -312,14 +303,11 @@ export type ResolveToParams<
       ? ResolveAllToParams<TRouter, TParamVariant>
       : TPath extends CatchAllPaths
         ? ResolveAllToParams<TRouter, TParamVariant>
-        : PostProcessParams<
-            ResolveRoute<
-              TRouter,
-              TFrom,
-              TTo
-            >['types'][ResolveToParamType<TParamVariant>],
-            TParamVariant
-          >
+        : ResolveRoute<
+            TRouter,
+            TFrom,
+            TTo
+          >['types'][ResolveToParamType<TParamVariant>]
     : never
 
 type ResolveRelativeToParams<
@@ -597,11 +585,6 @@ export function useLinkProps<
   // If this `to` is a valid external URL, return
   // null for LinkUtils
 
-  const dest = {
-    ...(options.to && { from: matchPathname }),
-    ...options,
-  }
-
   let type: 'internal' | 'external' = 'internal'
 
   try {
@@ -609,7 +592,7 @@ export function useLinkProps<
     type = 'external'
   } catch {}
 
-  const next = router.buildLocation(dest as any)
+  const next = router.buildLocation(options as any)
   const preload = userPreload ?? router.options.defaultPreload
   const preloadDelay =
     userPreloadDelay ?? router.options.defaultPreloadDelay ?? 0
@@ -695,7 +678,7 @@ export function useLinkProps<
   }
 
   const doPreload = () => {
-    router.preloadRoute(dest as any).catch((err) => {
+    router.preloadRoute(options as any).catch((err) => {
       console.warn(err)
       console.warn(preloadWarning)
     })
diff --git a/packages/react-router/src/route.ts b/packages/react-router/src/route.ts
index e6170f533..7b32a9b62 100644
--- a/packages/react-router/src/route.ts
+++ b/packages/react-router/src/route.ts
@@ -56,8 +56,6 @@ export type RouteOptions<
   TPath extends string = string,
   TSearchSchemaInput = Record<string, unknown>,
   TSearchSchema = {},
-  TSearchSchemaUsed = {},
-  TFullSearchSchemaInput = TSearchSchemaUsed,
   TFullSearchSchema = TSearchSchema,
   TParams = AnyPathParams,
   TAllParams = TParams,
@@ -74,8 +72,6 @@ export type RouteOptions<
   TPath,
   TSearchSchemaInput,
   TSearchSchema,
-  TSearchSchemaUsed,
-  TFullSearchSchemaInput,
   TFullSearchSchema,
   TParams,
   TAllParams,
@@ -170,8 +166,6 @@ export type BaseRouteOptions<
   TPath extends string = string,
   TSearchSchemaInput = Record<string, unknown>,
   TSearchSchema = {},
-  TSearchSchemaUsed = {},
-  TFullSearchSchemaInput = TSearchSchemaUsed,
   TFullSearchSchema = TSearchSchema,
   TParams = {},
   TAllParams = ParamsFallback<TPath, TParams>,
@@ -398,28 +392,15 @@ export type ResolveFullSearchSchema<
   TParentRoute extends AnyRoute,
   TSearchSchema,
 > = unknown extends TParentRoute
-  ? Omit<TSearchSchema, keyof RootSearchSchema>
-  : Assign<
-      TParentRoute['id'] extends RootRouteId
-        ? Omit<TParentRoute['types']['searchSchema'], keyof RootSearchSchema>
-        : TParentRoute['types']['fullSearchSchema'],
-      TSearchSchema
-    >
+  ? TSearchSchema
+  : Assign<TParentRoute['types']['fullSearchSchema'], TSearchSchema>
 
 export type ResolveFullSearchSchemaInput<
   TParentRoute extends AnyRoute,
   TSearchSchemaUsed,
 > = unknown extends TParentRoute
-  ? Omit<TSearchSchemaUsed, keyof RootSearchSchema>
-  : Assign<
-      TParentRoute['id'] extends RootRouteId
-        ? Omit<
-            TParentRoute['types']['searchSchemaInput'],
-            keyof RootSearchSchema
-          >
-        : TParentRoute['types']['fullSearchSchemaInput'],
-      TSearchSchemaUsed
-    >
+  ? TSearchSchemaUsed
+  : Assign<TParentRoute['types']['fullSearchSchemaInput'], TSearchSchemaUsed>
 
 export type ResolveRouteContext<TRouteContextReturn> = [
   TRouteContextReturn,
@@ -628,8 +609,6 @@ export class Route<
     TPath,
     TSearchSchemaInput,
     TSearchSchema,
-    TSearchSchemaUsed,
-    TFullSearchSchemaInput,
     TFullSearchSchema,
     TParams,
     TAllParams,
@@ -667,8 +646,6 @@ export class Route<
       TPath,
       TSearchSchemaInput,
       TSearchSchema,
-      TSearchSchemaUsed,
-      TFullSearchSchemaInput,
       TFullSearchSchema,
       TParams,
       TAllParams,
@@ -723,8 +700,6 @@ export class Route<
           TPath,
           TSearchSchemaInput,
           TSearchSchema,
-          TSearchSchemaUsed,
-          TFullSearchSchemaInput,
           TFullSearchSchema,
           TParams,
           TAllParams,
@@ -969,8 +944,6 @@ export function createRoute<
     TPath,
     TSearchSchemaInput,
     TSearchSchema,
-    TSearchSchemaUsed,
-    TFullSearchSchemaInput,
     TFullSearchSchema,
     TParams,
     TAllParams,
@@ -1010,9 +983,8 @@ export function createRoute<
 export type AnyRootRoute = RootRoute<any, any, any, any, any, any, any, any>
 
 export type RootRouteOptions<
-  TSearchSchemaInput = RootSearchSchema,
-  TSearchSchema = RootSearchSchema,
-  TSearchSchemaUsed = RootSearchSchema,
+  TSearchSchemaInput = {},
+  TSearchSchema = {},
   TRouteContextReturn = RouteContext,
   TRouteContext = ResolveRouteContext<TRouteContextReturn>,
   TRouterContext = {},
@@ -1026,8 +998,6 @@ export type RootRouteOptions<
     '', // TPath
     TSearchSchemaInput, // TSearchSchemaInput
     TSearchSchema, // TSearchSchema
-    TSearchSchemaUsed,
-    TSearchSchemaUsed, //TFullSearchSchemaInput
     TSearchSchema, // TFullSearchSchema
     {}, // TParams
     {}, // TAllParams
@@ -1049,13 +1019,15 @@ export type RootRouteOptions<
 
 export function createRootRouteWithContext<TRouterContext extends {}>() {
   return <
-    TSearchSchemaInput extends Record<string, any> = RootSearchSchema,
-    TSearchSchema extends Record<string, any> = RootSearchSchema,
-    TSearchSchemaUsed extends Record<string, any> = RootSearchSchema,
+    TSearchSchemaInput = {},
+    TSearchSchema = {},
+    TSearchSchemaUsed = ResolveSearchSchemaUsed<
+      TSearchSchemaInput,
+      TSearchSchema
+    >,
     TRouteContextReturn extends RouteContext = RouteContext,
-    TRouteContext extends RouteContext = [TRouteContextReturn] extends [never]
-      ? RouteContext
-      : TRouteContextReturn,
+    TRouteContext extends
+      RouteContext = ResolveRouteContext<TRouteContextReturn>,
     TLoaderDeps extends Record<string, any> = {},
     TLoaderDataReturn = {},
     TLoaderData = ResolveLoaderData<TLoaderDataReturn>,
@@ -1063,7 +1035,6 @@ export function createRootRouteWithContext<TRouterContext extends {}>() {
     options?: RootRouteOptions<
       TSearchSchemaInput,
       TSearchSchema,
-      TSearchSchemaUsed,
       TRouteContextReturn,
       TRouteContext,
       TRouterContext,
@@ -1090,14 +1061,10 @@ export function createRootRouteWithContext<TRouterContext extends {}>() {
  */
 export const rootRouteWithContext = createRootRouteWithContext
 
-export type RootSearchSchema = {
-  __TRootSearchSchema__: '__TRootSearchSchema__'
-}
-
 export class RootRoute<
-  in out TSearchSchemaInput = RootSearchSchema,
-  in out TSearchSchema = RootSearchSchema,
-  in out TSearchSchemaUsed = RootSearchSchema,
+  in out TSearchSchemaInput = {},
+  in out TSearchSchema = {},
+  in out TSearchSchemaUsed = {},
   TRouteContextReturn = RouteContext,
   in out TRouteContext = ResolveRouteContext<TRouteContextReturn>,
   in out TRouterContext = {},
@@ -1133,7 +1100,6 @@ export class RootRoute<
     options?: RootRouteOptions<
       TSearchSchemaInput,
       TSearchSchema,
-      TSearchSchemaUsed,
       TRouteContextReturn,
       TRouteContext,
       TRouterContext,
@@ -1147,9 +1113,12 @@ export class RootRoute<
 }
 
 export function createRootRoute<
-  TSearchSchemaInput = RootSearchSchema,
-  TSearchSchema = RootSearchSchema,
-  TSearchSchemaUsed = RootSearchSchema,
+  TSearchSchemaInput = {},
+  TSearchSchema = {},
+  TSearchSchemaUsed = ResolveSearchSchemaUsed<
+    TSearchSchemaInput,
+    TSearchSchema
+  >,
   TRouteContextReturn = RouteContext,
   TRouteContext = ResolveRouteContext<TRouteContextReturn>,
   TRouterContext = {},
@@ -1164,9 +1133,7 @@ export function createRootRoute<
       '', // TPath
       TSearchSchemaInput, // TSearchSchemaInput
       TSearchSchema, // TSearchSchema
-      TSearchSchemaUsed,
-      TSearchSchemaUsed, // TFullSearchSchemaInput
-      TSearchSchema, // TFullSearchSchema
+      TSearchSchema,
       {}, // TParams
       {}, // TAllParams
       TRouteContextReturn, // TRouteContextReturn
@@ -1306,10 +1273,7 @@ export class NotFoundRoute<
   TFullSearchSchema = ResolveFullSearchSchema<TParentRoute, TSearchSchema>,
   TRouteContextReturn = AnyContext,
   TRouteContext = RouteContext,
-  TAllContext = Assign<
-    IsAny<TParentRoute['types']['allContext'], {}>,
-    TRouteContext
-  >,
+  TAllContext = ResolveAllContext<TParentRoute, TRouteContext>,
   TRouterContext = AnyContext,
   TLoaderDeps extends Record<string, any> = {},
   TLoaderDataReturn = {},
@@ -1345,8 +1309,6 @@ export class NotFoundRoute<
         string,
         TSearchSchemaInput,
         TSearchSchema,
-        TSearchSchemaUsed,
-        TFullSearchSchemaInput,
         TFullSearchSchema,
         {},
         {},
diff --git a/packages/react-router/src/routeInfo.ts b/packages/react-router/src/routeInfo.ts
index f9d5f51b7..fea4d0219 100644
--- a/packages/react-router/src/routeInfo.ts
+++ b/packages/react-router/src/routeInfo.ts
@@ -1,4 +1,4 @@
-import type { AnyRoute, RootSearchSchema } from './route'
+import type { AnyRoute } from './route'
 import type { AnyRouter, TrailingSlashOption } from './router'
 import type { Expand, MergeUnion } from './utils'
 
@@ -115,14 +115,11 @@ export type RouteByToPath<TRouter extends AnyRouter, TTo> = Extract<
 >
 
 export type FullSearchSchema<TRouteTree extends AnyRoute> = MergeUnion<
-  Exclude<ParseRoute<TRouteTree>['types']['fullSearchSchema'], RootSearchSchema>
+  ParseRoute<TRouteTree>['types']['fullSearchSchema']
 >
 
 export type FullSearchSchemaInput<TRouteTree extends AnyRoute> = MergeUnion<
-  Exclude<
-    ParseRoute<TRouteTree>['types']['fullSearchSchemaInput'],
-    RootSearchSchema
-  >
+  ParseRoute<TRouteTree>['types']['fullSearchSchemaInput']
 >
 
 export type AllParams<TRouteTree extends AnyRoute> = MergeUnion<
diff --git a/packages/react-router/src/router.ts b/packages/react-router/src/router.ts
index 2d1abad62..d72bbb294 100644
--- a/packages/react-router/src/router.ts
+++ b/packages/react-router/src/router.ts
@@ -380,6 +380,7 @@ export interface BuildNextOptions {
   }
   from?: string
   fromSearch?: unknown
+  _fromLocation?: ParsedLocation
 }
 
 export interface DehydratedRouterState {
@@ -1100,16 +1101,31 @@ export class Router<
       } = {},
       matches?: Array<MakeRouteMatch<TRouteTree>>,
     ): ParsedLocation => {
-      let fromPath = this.latestLocation.pathname
-      let fromSearch = dest.fromSearch || this.latestLocation.search
+      const latestLocation =
+        dest._fromLocation ?? (this.latestLocation as ParsedLocation)
+      let fromPath = latestLocation.pathname
+      let fromSearch = dest.fromSearch || latestLocation.search
+
+      const fromMatches = this.matchRoutes(latestLocation.pathname, fromSearch)
+
+      const fromMatch =
+        dest.from != null
+          ? fromMatches.find((d) =>
+              matchPathname(this.basepath, trimPathRight(d.pathname), {
+                to: dest.from,
+                caseSensitive: false,
+                fuzzy: false,
+              }),
+            )
+          : undefined
+
+      fromPath = fromMatch?.pathname || fromPath
 
-      const fromMatches = this.matchRoutes(
-        this.latestLocation.pathname,
-        fromSearch,
+      invariant(
+        dest.from == null || fromMatch != null,
+        'Could not find match for from: ' + dest.from,
       )
 
-      fromPath =
-        fromMatches.find((d) => d.id === dest.from)?.pathname || fromPath
       fromSearch = last(fromMatches)?.search || this.latestLocation.search
 
       const stayingMatches = matches?.filter((d) =>
@@ -1260,9 +1276,10 @@ export class Router<
         })
 
         if (foundMask) {
+          const { from, ...maskProps } = foundMask
           maskedDest = {
             ...pick(opts, ['from']),
-            ...foundMask,
+            ...maskProps,
             params,
           }
           maskedNext = build(maskedDest)
@@ -1774,7 +1791,7 @@ export class Router<
                     context: parentContext,
                     location,
                     navigate: (opts: any) =>
-                      this.navigate({ ...opts, from: match.pathname }),
+                      this.navigate({ ...opts, _fromLocation: location }),
                     buildLocation: this.buildLocation,
                     cause: preload ? 'preload' : match.cause,
                   })) ?? ({} as any)
@@ -1828,7 +1845,7 @@ export class Router<
                   context: match.context,
                   location,
                   navigate: (opts) =>
-                    this.navigate({ ...opts, from: match.pathname } as any),
+                    this.navigate({ ...opts, _fromLocation: location }),
                   cause: preload ? 'preload' : match.cause,
                   route,
                 }
@@ -2183,9 +2200,8 @@ export class Router<
     } catch (err) {
       if (isRedirect(err)) {
         return await this.preloadRoute({
-          fromSearch: next.search,
-          from: next.pathname,
           ...(err as any),
+          _fromLocation: next,
         })
       }
       // Preload errors are not fatal, but we should still log them
diff --git a/packages/react-router/src/useNavigate.tsx b/packages/react-router/src/useNavigate.tsx
index 52d048b30..93b8bdbd8 100644
--- a/packages/react-router/src/useNavigate.tsx
+++ b/packages/react-router/src/useNavigate.tsx
@@ -29,7 +29,6 @@ export function useNavigate<
     (options: NavigateOptions) => {
       return router.navigate({
         ...options,
-        from: options.to ? router.state.resolvedLocation.pathname : undefined,
       })
     },
     [router],
@@ -63,7 +62,6 @@ export function Navigate<
 
   React.useEffect(() => {
     navigate({
-      from: props.to ? match.pathname : undefined,
       ...props,
     } as any)
     // eslint-disable-next-line react-hooks/exhaustive-deps
diff --git a/packages/react-router/src/useSearch.tsx b/packages/react-router/src/useSearch.tsx
index 4d2fc778c..d4dbcebcb 100644
--- a/packages/react-router/src/useSearch.tsx
+++ b/packages/react-router/src/useSearch.tsx
@@ -1,9 +1,9 @@
 import { useMatch } from './useMatch'
-import type { AnyRoute, RootSearchSchema } from './route'
+import type { AnyRoute } from './route'
 import type { FullSearchSchema, RouteById, RouteIds } from './routeInfo'
 import type { RegisteredRouter } from './router'
 import type { MakeRouteMatch } from './Matches'
-import type { StrictOrFrom } from './utils'
+import type { Expand, StrictOrFrom } from './utils'
 
 export type UseSearchOptions<
   TFrom,
@@ -20,10 +20,7 @@ export function useSearch<
   TStrict extends boolean = true,
   TSearch = TStrict extends false
     ? FullSearchSchema<TRouteTree>
-    : Exclude<
-        RouteById<TRouteTree, TFrom>['types']['fullSearchSchema'],
-        RootSearchSchema
-      >,
+    : Expand<RouteById<TRouteTree, TFrom>['types']['fullSearchSchema']>,
   TSelected = TSearch,
 >(opts: UseSearchOptions<TFrom, TStrict, TSearch, TSelected>): TSelected {
   return useMatch({
diff --git a/packages/router-cli/package.json b/packages/router-cli/package.json
index f2dbc5811..2fa261a72 100644
--- a/packages/router-cli/package.json
+++ b/packages/router-cli/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@tanstack/router-cli",
-  "version": "1.34.8",
+  "version": "1.35.4",
   "description": "",
   "author": "Tanner Linsley",
   "license": "MIT",
diff --git a/packages/router-devtools/package.json b/packages/router-devtools/package.json
index a850b6b7b..091641be3 100644
--- a/packages/router-devtools/package.json
+++ b/packages/router-devtools/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@tanstack/router-devtools",
-  "version": "1.35.3",
+  "version": "1.35.5",
   "description": "",
   "author": "Tanner Linsley",
   "license": "MIT",
diff --git a/packages/router-generator/package.json b/packages/router-generator/package.json
index 4da61f005..5581f24b6 100644
--- a/packages/router-generator/package.json
+++ b/packages/router-generator/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@tanstack/router-generator",
-  "version": "1.34.8",
+  "version": "1.35.4",
   "description": "",
   "author": "Tanner Linsley",
   "license": "MIT",
diff --git a/packages/router-generator/src/generator.ts b/packages/router-generator/src/generator.ts
index bf5ed138f..84761cf1f 100644
--- a/packages/router-generator/src/generator.ts
+++ b/packages/router-generator/src/generator.ts
@@ -8,6 +8,7 @@ import type { Config } from './config'
 let latestTask = 0
 export const rootPathId = '__root'
 const routeGroupPatternRegex = /\(.+\)/g
+const possiblyNestedRouteGroupPatternRegex = /\([^/]+\)\/?/g
 
 export type RouteNode = {
   filePath: string
@@ -774,7 +775,7 @@ function replaceBackslash(s: string) {
 }
 
 function removeGroups(s: string) {
-  return s.replaceAll(routeGroupPatternRegex, '').replaceAll('//', '/')
+  return s.replace(possiblyNestedRouteGroupPatternRegex, '')
 }
 
 /**
diff --git a/packages/router-vite-plugin/package.json b/packages/router-vite-plugin/package.json
index 1f4b7754b..2e24f008c 100644
--- a/packages/router-vite-plugin/package.json
+++ b/packages/router-vite-plugin/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@tanstack/router-vite-plugin",
-  "version": "1.34.8",
+  "version": "1.35.4",
   "description": "",
   "author": "Tanner Linsley",
   "license": "MIT",
diff --git a/packages/start/package.json b/packages/start/package.json
index 20b07a633..1df65a7cc 100644
--- a/packages/start/package.json
+++ b/packages/start/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@tanstack/start",
-  "version": "1.35.3",
+  "version": "1.35.5",
   "description": "",
   "author": "Tanner Linsley",
   "license": "MIT",


Test patch:
diff --git a/packages/react-router/tests/link.test-d.tsx b/packages/react-router/tests/link.test-d.tsx
index 98724329e..1aa58f82b 100644
--- a/packages/react-router/tests/link.test-d.tsx
+++ b/packages/react-router/tests/link.test-d.tsx
@@ -2,14 +2,17 @@ import { expectTypeOf, test } from 'vitest'
 import { Link, createRoute, createRouter } from '../src'
 import { createRootRoute } from '../src'
 
-const rootRoute = createRootRoute()
+const rootRoute = createRootRoute({
+  validateSearch: (): { rootPage?: number } => ({ rootPage: 0 }),
+})
 
 const indexRoute = createRoute({
   getParentRoute: () => rootRoute,
   path: '/',
+  validateSearch: () => ({ rootIndexPage: 0 }),
 })
 
-export const postsRoute = createRoute({
+const postsRoute = createRoute({
   getParentRoute: () => rootRoute,
   path: 'posts',
 })
@@ -114,7 +117,25 @@ type RouterNeverTrailingSlashes = typeof routerNeverTrailingSlashes
 type RouterPreserveTrailingSlashes = typeof routerPreserveTrailingSlashes
 
 test('when navigating to the root', () => {
-  expectTypeOf(Link<DefaultRouter, string, '/'>)
+  const DefaultRouterLink = Link<DefaultRouter, string, '/'>
+  const DefaultRouterObjectsLink = Link<DefaultRouterObjects, string, '/'>
+  const RouterAlwaysTrailingSlashLink = Link<
+    RouterAlwaysTrailingSlashes,
+    string,
+    '/'
+  >
+  const RouterNeverTrailingSlashLink = Link<
+    RouterNeverTrailingSlashes,
+    string,
+    '/'
+  >
+  const RouterPreserveTrailingSlashLink = Link<
+    RouterPreserveTrailingSlashes,
+    string,
+    '/'
+  >
+
+  expectTypeOf(DefaultRouterLink)
     .parameter(0)
     .toHaveProperty('to')
     .toEqualTypeOf<
@@ -131,7 +152,7 @@ test('when navigating to the root', () => {
       | undefined
     >()
 
-  expectTypeOf(Link<DefaultRouterObjects, string, '/'>)
+  expectTypeOf(DefaultRouterObjectsLink)
     .parameter(0)
     .toHaveProperty('to')
     .toEqualTypeOf<
@@ -148,7 +169,7 @@ test('when navigating to the root', () => {
       | undefined
     >()
 
-  expectTypeOf(Link<RouterAlwaysTrailingSlashes, string, '/'>)
+  expectTypeOf(RouterAlwaysTrailingSlashLink)
     .parameter(0)
     .toHaveProperty('to')
     .toEqualTypeOf<
@@ -165,7 +186,7 @@ test('when navigating to the root', () => {
       | undefined
     >()
 
-  expectTypeOf(Link<RouterNeverTrailingSlashes, string, '/'>)
+  expectTypeOf(RouterNeverTrailingSlashLink)
     .parameter(0)
     .toHaveProperty('to')
     .toEqualTypeOf<
@@ -182,7 +203,7 @@ test('when navigating to the root', () => {
       | undefined
     >()
 
-  expectTypeOf(Link<RouterPreserveTrailingSlashes, string, '/'>)
+  expectTypeOf(RouterPreserveTrailingSlashLink)
     .parameter(0)
     .toHaveProperty('to')
     .toEqualTypeOf<
@@ -205,6 +226,131 @@ test('when navigating to the root', () => {
       | '/posts/$postId/'
       | undefined
     >()
+
+  expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toMatchTypeOf<{ search: unknown }>()
+
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toMatchTypeOf<{ search: unknown }>()
+
+  expectTypeOf(RouterAlwaysTrailingSlashLink)
+    .parameter(0)
+    .toMatchTypeOf<{ search: unknown }>()
+
+  expectTypeOf(RouterNeverTrailingSlashLink)
+    .parameter(0)
+    .toMatchTypeOf<{ search: unknown }>()
+
+  expectTypeOf(RouterPreserveTrailingSlashLink)
+    .parameter(0)
+    .toMatchTypeOf<{ search: unknown }>()
+
+  expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
+
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
+
+  expectTypeOf(RouterAlwaysTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
+
+  expectTypeOf(RouterNeverTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
+
+  expectTypeOf(RouterPreserveTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
+
+  expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .parameter(0)
+    .toEqualTypeOf<{
+      page?: number
+      rootIndexPage?: number
+      rootPage?: number
+    }>()
+
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .parameter(0)
+    .toEqualTypeOf<{
+      page?: number
+      rootIndexPage?: number
+      rootPage?: number
+    }>()
+
+  expectTypeOf(RouterAlwaysTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .parameter(0)
+    .toEqualTypeOf<{
+      page?: number
+      rootIndexPage?: number
+      rootPage?: number
+    }>()
+
+  expectTypeOf(RouterNeverTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .parameter(0)
+    .toEqualTypeOf<{
+      page?: number
+      rootIndexPage?: number
+      rootPage?: number
+    }>()
+
+  expectTypeOf(RouterPreserveTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .parameter(0)
+    .toEqualTypeOf<{
+      page?: number
+      rootIndexPage?: number
+      rootPage?: number
+    }>()
+
+  expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .returns.toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
+
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .returns.toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
+
+  expectTypeOf(RouterAlwaysTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .returns.toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
+
+  expectTypeOf(RouterNeverTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .returns.toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
+
+  expectTypeOf(RouterPreserveTrailingSlashLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .returns.toEqualTypeOf<{ rootPage?: number; rootIndexPage: number }>()
 })
 
 test('when navigating from a route with no params and no search to the root', () => {
@@ -2041,53 +2187,78 @@ test('when navigating to a route with search params', () => {
 
   defaultRouterLinkSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page: number }>()
+    .toEqualTypeOf<{ rootPage?: number; page: number }>()
 
   defaultRouterObjectsLinkSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page: number }>()
+    .toEqualTypeOf<{ rootPage?: number; page: number }>()
 
   routerAlwaysTrailingSlashesLinkSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page: number }>()
+    .toEqualTypeOf<{ rootPage?: number; page: number }>()
 
   routerNeverTrailingSlashesLinkSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page: number }>()
+    .toEqualTypeOf<{ rootPage?: number; page: number }>()
 
   routerPreserveTrailingSlashesLinkSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page: number }>()
+    .toEqualTypeOf<{ rootPage?: number; page: number }>()
 
-  defaultRouterLinkSearch.returns.toEqualTypeOf<{ page: number }>()
+  defaultRouterLinkSearch.returns.toEqualTypeOf<{
+    page: number
+    rootPage?: number
+  }>()
 
-  defaultRouterObjectsLinkSearch.returns.toEqualTypeOf<{ page: number }>()
+  defaultRouterObjectsLinkSearch.returns.toEqualTypeOf<{
+    page: number
+    rootPage?: number
+  }>()
 
   routerAlwaysTrailingSlashesLinkSearch.returns.toEqualTypeOf<{
     page: number
+    rootPage?: number
   }>()
 
-  routerNeverTrailingSlashesLinkSearch.returns.toEqualTypeOf<{ page: number }>()
+  routerNeverTrailingSlashesLinkSearch.returns.toEqualTypeOf<{
+    rootPage?: number
+    page: number
+  }>()
 
   routerPreserveTrailingSlashesLinkSearch.returns.toEqualTypeOf<{
     page: number
+    rootPage?: number
   }>()
 
-  defaultRouterLinkSearch.parameter(0).toEqualTypeOf<{ page?: number }>()
+  defaultRouterLinkSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
 
-  defaultRouterObjectsLinkSearch.parameter(0).toEqualTypeOf<{ page?: number }>()
+  defaultRouterObjectsLinkSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
 
-  routerAlwaysTrailingSlashesLinkSearch
-    .parameter(0)
-    .toEqualTypeOf<{ page?: number }>()
+  routerAlwaysTrailingSlashesLinkSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
 
-  routerNeverTrailingSlashesLinkSearch
-    .parameter(0)
-    .toEqualTypeOf<{ page?: number }>()
+  routerNeverTrailingSlashesLinkSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
 
-  routerPreserveTrailingSlashesLinkSearch
-    .parameter(0)
-    .toEqualTypeOf<{ page?: number }>()
+  routerPreserveTrailingSlashesLinkSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
 })
 
 test('when navigating to a route with optional search params', () => {
@@ -2168,57 +2339,106 @@ test('when navigating to a route with optional search params', () => {
     .parameter(0)
     .not.toMatchTypeOf<{ search: unknown }>()
 
-  defaultRouterLinkSearch
-    .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page?: number | undefined } | undefined>()
+  defaultRouterLinkSearch.exclude<Function | boolean>().toEqualTypeOf<
+    | {
+        rootPage?: number
+        page?: number
+      }
+    | undefined
+  >()
 
-  defaultRouterObjectsLinkSearch
-    .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page?: number | undefined } | undefined>()
+  defaultRouterObjectsLinkSearch.exclude<Function | boolean>().toEqualTypeOf<
+    | {
+        rootPage?: number
+        page?: number
+      }
+    | undefined
+  >()
 
   routerAlwaysTrailingSlashesLinkSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page?: number | undefined } | undefined>()
+    .toEqualTypeOf<
+      | {
+          rootPage?: number
+          page?: number
+        }
+      | undefined
+    >()
 
   routerNeverTrailingSlashesLinkSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page?: number | undefined } | undefined>()
+    .toEqualTypeOf<
+      | {
+          rootPage?: number
+          page?: number
+        }
+      | undefined
+    >()
 
   routerPreserveTrailingSlashesLinkSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page?: number | undefined } | undefined>()
+    .toEqualTypeOf<
+      | {
+          rootPage?: number
+          page?: number
+        }
+      | undefined
+    >()
 
-  defaultRouterLinkSearch.returns.toEqualTypeOf<{ page?: number }>()
+  defaultRouterLinkSearch.returns.toEqualTypeOf<{
+    rootPage?: number
+    page?: number
+  }>()
 
-  defaultRouterObjectsLinkSearch.returns.toEqualTypeOf<{ page?: number }>()
+  defaultRouterObjectsLinkSearch.returns.toEqualTypeOf<{
+    rootPage?: number
+    page?: number
+  }>()
 
   routerAlwaysTrailingSlashesLinkSearch.returns.toEqualTypeOf<{
+    rootPage?: number
     page?: number
   }>()
 
   routerNeverTrailingSlashesLinkSearch.returns.toEqualTypeOf<{
+    rootPage?: number
     page?: number
   }>()
 
   routerPreserveTrailingSlashesLinkSearch.returns.toEqualTypeOf<{
+    rootPage?: number
     page?: number
   }>()
 
-  defaultRouterLinkSearch.parameter(0).toEqualTypeOf<{ page?: number }>()
+  defaultRouterLinkSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
 
-  defaultRouterObjectsLinkSearch.parameter(0).toEqualTypeOf<{ page?: number }>()
+  defaultRouterObjectsLinkSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
 
-  routerAlwaysTrailingSlashesLinkSearch
-    .parameter(0)
-    .toEqualTypeOf<{ page?: number }>()
+  routerAlwaysTrailingSlashesLinkSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
 
-  routerNeverTrailingSlashesLinkSearch
-    .parameter(0)
-    .toEqualTypeOf<{ page?: number }>()
+  routerNeverTrailingSlashesLinkSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
 
-  routerPreserveTrailingSlashesLinkSearch
-    .parameter(0)
-    .toEqualTypeOf<{ page?: number }>()
+  routerPreserveTrailingSlashesLinkSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
 })
 
 test('when navigating from a route with no search params to a route with search params', () => {
@@ -2298,47 +2518,72 @@ test('when navigating from a route with no search params to a route with search
     .parameter(0)
     .toMatchTypeOf<{ search: unknown }>()
 
-  defaultRouterLinkSearch
-    .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page: number }>()
+  defaultRouterLinkSearch.exclude<Function | boolean>().toEqualTypeOf<{
+    rootPage?: number
+    page: number
+  }>()
 
-  defaultRouterObjectsLinkSearch
-    .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page: number }>()
+  defaultRouterObjectsLinkSearch.exclude<Function | boolean>().toEqualTypeOf<{
+    rootPage?: number
+    page: number
+  }>()
 
   routerAlwaysTrailingSlashesLinkSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page: number }>()
+    .toEqualTypeOf<{
+      rootPage?: number
+      page: number
+    }>()
 
   routerNeverTrailingSlashesLinkSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page: number }>()
+    .toEqualTypeOf<{
+      rootPage?: number
+      page: number
+    }>()
 
-  defaultRouterLinkSearch.returns.toEqualTypeOf<{ page: number }>()
+  defaultRouterLinkSearch.returns.toEqualTypeOf<{
+    rootPage?: number
+    page: number
+  }>()
 
-  defaultRouterObjectsLinkSearch.returns.toEqualTypeOf<{ page: number }>()
+  defaultRouterObjectsLinkSearch.returns.toEqualTypeOf<{
+    rootPage?: number
+    page: number
+  }>()
 
   routerAlwaysTrailingSlashesLinkSearch.returns.toEqualTypeOf<{
+    rootPage?: number
     page: number
   }>()
 
   routerNeverTrailingSlashesLinkSearch.returns.toEqualTypeOf<{
+    rootPage?: number
     page: number
   }>()
 
   routerPreserveTrailingSlashesLinkSearch.returns.toEqualTypeOf<{
+    rootPage?: number
     page: number
   }>()
 
-  defaultRouterLinkSearch.parameter(0).toEqualTypeOf<{}>()
+  defaultRouterLinkSearch.parameter(0).toEqualTypeOf<{ rootPage?: number }>()
 
-  defaultRouterObjectsLinkSearch.parameter(0).toEqualTypeOf<{}>()
+  defaultRouterObjectsLinkSearch
+    .parameter(0)
+    .toEqualTypeOf<{ rootPage?: number }>()
 
-  routerAlwaysTrailingSlashesLinkSearch.parameter(0).toEqualTypeOf<{}>()
+  routerAlwaysTrailingSlashesLinkSearch
+    .parameter(0)
+    .toEqualTypeOf<{ rootPage?: number }>()
 
-  routerNeverTrailingSlashesLinkSearch.parameter(0).toEqualTypeOf<{}>()
+  routerNeverTrailingSlashesLinkSearch
+    .parameter(0)
+    .toEqualTypeOf<{ rootPage?: number }>()
 
-  routerPreserveTrailingSlashesLinkSearch.parameter(0).toEqualTypeOf<{}>()
+  routerPreserveTrailingSlashesLinkSearch
+    .parameter(0)
+    .toEqualTypeOf<{ rootPage?: number }>()
 })
 
 test('when navigating to a union of routes with search params', () => {
@@ -2423,71 +2668,83 @@ test('when navigating to a union of routes with search params', () => {
 
   defaultRouterLinkSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page: number } | {} | undefined>()
+    .toEqualTypeOf<
+      { rootPage?: number; page: number } | { rootPage?: number } | undefined
+    >()
 
   defaultRouterObjectsLinkSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page: number } | {} | undefined>()
+    .toEqualTypeOf<
+      { rootPage?: number; page: number } | { rootPage?: number } | undefined
+    >()
 
   routerAlwaysTrailingSlashesSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page: number } | {} | undefined>()
+    .toEqualTypeOf<
+      { rootPage?: number; page: number } | { rootPage?: number } | undefined
+    >()
 
   routerNeverTrailingSlashesSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page: number } | {} | undefined>()
+    .toEqualTypeOf<
+      { rootPage?: number; page: number } | { rootPage?: number } | undefined
+    >()
 
   routerPreserveTrailingSlashesSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page: number } | {} | undefined>()
-
-  defaultRouterLinkSearch.returns.toEqualTypeOf<{ page: number } | {}>()
-
-  defaultRouterObjectsLinkSearch.returns.toEqualTypeOf<{ page: number } | {}>()
-
-  routerAlwaysTrailingSlashesSearch.returns.toEqualTypeOf<
-    { page: number } | {}
-  >()
+    .toEqualTypeOf<
+      { rootPage?: number; page: number } | { rootPage?: number } | undefined
+    >()
 
-  routerNeverTrailingSlashesSearch.returns.toEqualTypeOf<
-    { page: number } | {}
+  defaultRouterLinkSearch.returns.toEqualTypeOf<
+    { rootPage?: number; page: number } | { rootPage?: number }
   >()
 
-  routerPreserveTrailingSlashesSearch.returns.toEqualTypeOf<
-    { page: number } | {}
+  defaultRouterObjectsLinkSearch.returns.toEqualTypeOf<
+    { rootPage?: number; page: number } | { rootPage?: number }
   >()
 
-  defaultRouterLinkSearch.returns.toEqualTypeOf<{ page: number } | {}>()
-
-  defaultRouterObjectsLinkSearch.returns.toEqualTypeOf<{ page: number } | {}>()
-
   routerAlwaysTrailingSlashesSearch.returns.toEqualTypeOf<
-    { page: number } | {}
+    { rootPage?: number; page: number } | { rootPage?: number }
   >()
 
   routerNeverTrailingSlashesSearch.returns.toEqualTypeOf<
-    { page: number } | {}
+    { rootPage?: number; page: number } | { rootPage?: number }
   >()
 
   routerPreserveTrailingSlashesSearch.returns.toEqualTypeOf<
-    { page: number } | {}
+    { rootPage?: number; page: number } | { rootPage?: number }
   >()
 
-  defaultRouterLinkSearch.parameter(0).toEqualTypeOf<{ page?: number }>()
+  defaultRouterLinkSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
 
-  defaultRouterObjectsLinkSearch.parameter(0).toEqualTypeOf<{ page?: number }>()
+  defaultRouterObjectsLinkSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
 
-  routerAlwaysTrailingSlashesSearch
-    .parameter(0)
-    .toEqualTypeOf<{ page?: number }>()
+  routerAlwaysTrailingSlashesSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
 
-  routerNeverTrailingSlashesSearch
-    .parameter(0)
-    .toEqualTypeOf<{ page?: number }>()
+  routerNeverTrailingSlashesSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
 
-  routerPreserveTrailingSlashesSearch
-    .parameter(0)
-    .toEqualTypeOf<{ page?: number }>()
+  routerPreserveTrailingSlashesSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
 })
 
 test('when navigating to a union of routes with search params including the root', () => {
@@ -2498,7 +2755,7 @@ test('when navigating to a union of routes with search params including the root
   >
 
   const DefaultRouterObjectsLink = Link<
-    DefaultRouter,
+    DefaultRouterObjects,
     string,
     '/' | '/invoices/$invoiceId/edit' | '/posts/$postId'
   >
@@ -2573,53 +2830,243 @@ test('when navigating to a union of routes with search params including the root
 
   defaultRouterSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page: number } | {} | undefined>()
+    .toEqualTypeOf<
+      | { rootPage?: number }
+      | { rootPage?: number; page: number }
+      | { rootPage?: number; rootIndexPage: number }
+      | undefined
+    >()
 
   defaultRouterObjectsSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page: number } | {} | undefined>()
+    .toEqualTypeOf<
+      | { rootPage?: number }
+      | { rootPage?: number; page: number }
+      | { rootPage?: number; rootIndexPage: number }
+      | undefined
+    >()
 
   routerAlwaysTrailingSlashesLinkSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page: number } | {} | undefined>()
+    .toEqualTypeOf<
+      | { rootPage?: number }
+      | { rootPage?: number; page: number }
+      | { rootPage?: number; rootIndexPage: number }
+      | undefined
+    >()
 
   routerNeverTrailingSlashesLinkSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page: number } | {} | undefined>()
+    .toEqualTypeOf<
+      | { rootPage?: number }
+      | { rootPage?: number; page: number }
+      | { rootPage?: number; rootIndexPage: number }
+      | undefined
+    >()
 
   routerPreserveTrailingSlashesLinkSearch
     .exclude<Function | boolean>()
-    .toEqualTypeOf<{ page: number } | {} | undefined>()
+    .toEqualTypeOf<
+      | { rootPage?: number }
+      | { rootPage?: number; page: number }
+      | { rootPage?: number; rootIndexPage: number }
+      | undefined
+    >()
 
-  defaultRouterSearch.returns.toEqualTypeOf<{ page: number } | {}>()
+  defaultRouterSearch.returns.toEqualTypeOf<
+    | { rootPage?: number; page: number }
+    | { rootPage?: number; rootIndexPage: number }
+    | { rootPage?: number }
+  >()
 
-  defaultRouterObjectsSearch.returns.toEqualTypeOf<{ page: number } | {}>()
+  defaultRouterObjectsSearch.returns.toEqualTypeOf<
+    | { rootPage?: number; page: number }
+    | { rootPage?: number; rootIndexPage: number }
+    | { rootPage?: number }
+  >()
 
   routerAlwaysTrailingSlashesLinkSearch.returns.toEqualTypeOf<
-    { page: number } | {}
+    | { rootPage?: number; page: number }
+    | { rootPage?: number; rootIndexPage: number }
+    | { rootPage?: number }
   >()
 
   routerNeverTrailingSlashesLinkSearch.returns.toEqualTypeOf<
-    { page: number } | {}
+    | { rootPage?: number; page: number }
+    | { rootPage?: number; rootIndexPage: number }
+    | { rootPage?: number }
   >()
 
   routerPreserveTrailingSlashesLinkSearch.returns.toEqualTypeOf<
-    { page: number } | {}
+    | { rootPage?: number; page: number }
+    | { rootPage?: number; rootIndexPage: number }
+    | { rootPage?: number }
   >()
 
-  defaultRouterSearch.parameter(0).toEqualTypeOf<{ page?: number }>()
+  defaultRouterSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
+
+  defaultRouterObjectsSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
 
-  defaultRouterObjectsSearch.parameter(0).toEqualTypeOf<{ page?: number }>()
+  routerAlwaysTrailingSlashesLinkSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
 
-  routerAlwaysTrailingSlashesLinkSearch
+  routerNeverTrailingSlashesLinkSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
+
+  routerPreserveTrailingSlashesLinkSearch.parameter(0).toEqualTypeOf<{
+    page?: number
+    rootIndexPage?: number
+    rootPage?: number
+  }>()
+})
+
+test('when navigating from the root to /posts', () => {
+  const DefaultRouterLink = Link<DefaultRouter, '/', '/posts'>
+
+  const DefaultRouterObjectsLink = Link<DefaultRouterObjects, '/', '/posts'>
+
+  const RouterAlwaysTrailingSlashesLink = Link<
+    RouterAlwaysTrailingSlashes,
+    '/',
+    '/posts/'
+  >
+
+  const RouterNeverTrailingSlashesLink = Link<
+    RouterNeverTrailingSlashes,
+    '/',
+    '/posts'
+  >
+
+  const RouterPreserveTrailingSlashesLink = Link<
+    RouterPreserveTrailingSlashes,
+    '/',
+    '/posts' | '/posts/'
+  >
+
+  expectTypeOf(DefaultRouterLink).not.toMatchTypeOf<{ search: unknown }>()
+
+  expectTypeOf(DefaultRouterObjectsLink).not.toMatchTypeOf<{
+    search: unknown
+  }>()
+
+  expectTypeOf(RouterAlwaysTrailingSlashesLink).not.toMatchTypeOf<{
+    search: unknown
+  }>()
+
+  expectTypeOf(RouterNeverTrailingSlashesLink).not.toMatchTypeOf<{
+    search: unknown
+  }>()
+
+  expectTypeOf(RouterPreserveTrailingSlashesLink).not.toMatchTypeOf<{
+    search: unknown
+  }>()
+
+  expectTypeOf(DefaultRouterLink)
     .parameter(0)
-    .toEqualTypeOf<{ page?: number }>()
+    .toHaveProperty('search')
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ rootPage?: number } | undefined>()
 
-  routerNeverTrailingSlashesLinkSearch
+  expectTypeOf(DefaultRouterObjectsLink)
     .parameter(0)
-    .toEqualTypeOf<{ page?: number }>()
+    .toHaveProperty('search')
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ rootPage?: number } | undefined>()
 
-  routerPreserveTrailingSlashesLinkSearch
+  expectTypeOf(RouterAlwaysTrailingSlashesLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ rootPage?: number } | undefined>()
+
+  expectTypeOf(RouterNeverTrailingSlashesLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ rootPage?: number } | undefined>()
+
+  expectTypeOf(RouterPreserveTrailingSlashesLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ rootPage?: number } | undefined>()
+
+  expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .parameter(0)
+    .toEqualTypeOf<
+      { rootPage?: number } | { rootPage?: number; rootIndexPage: number }
+    >()
+
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .parameter(0)
+    .toEqualTypeOf<
+      { rootPage?: number } | { rootPage?: number; rootIndexPage: number }
+    >()
+
+  expectTypeOf(RouterAlwaysTrailingSlashesLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .parameter(0)
+    .toEqualTypeOf<
+      { rootPage?: number } | { rootPage?: number; rootIndexPage: number }
+    >()
+
+  expectTypeOf(RouterNeverTrailingSlashesLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .parameter(0)
+    .toEqualTypeOf<
+      { rootPage?: number } | { rootPage?: number; rootIndexPage: number }
+    >()
+
+  expectTypeOf(RouterPreserveTrailingSlashesLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .parameter(0)
+    .toEqualTypeOf<
+      { rootPage?: number } | { rootPage?: number; rootIndexPage: number }
+    >()
+
+  expectTypeOf(DefaultRouterLink)
     .parameter(0)
-    .toEqualTypeOf<{ page?: number }>()
+    .toHaveProperty('search')
+    .returns.toEqualTypeOf<{ rootPage?: number }>()
+
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .returns.toEqualTypeOf<{ rootPage?: number }>()
+
+  expectTypeOf(RouterAlwaysTrailingSlashesLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .returns.toEqualTypeOf<{ rootPage?: number }>()
+
+  expectTypeOf(RouterNeverTrailingSlashesLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .returns.toEqualTypeOf<{ rootPage?: number }>()
+
+  expectTypeOf(RouterPreserveTrailingSlashesLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .returns.toEqualTypeOf<{ rootPage?: number }>()
 })
diff --git a/packages/react-router/tests/link.test.tsx b/packages/react-router/tests/link.test.tsx
index 9ebcf2045..0fd39865a 100644
--- a/packages/react-router/tests/link.test.tsx
+++ b/packages/react-router/tests/link.test.tsx
@@ -19,11 +19,12 @@ import {
   createRootRouteWithContext,
   createRoute,
   createRouter,
+  createRouteMask,
   redirect,
   useLoaderData,
   useParams,
-  useRouteContext,
   useSearch,
+  useRouteContext,
 } from '../src'
 
 afterEach(() => {
@@ -1090,6 +1091,1453 @@ describe('Link', () => {
 
     expect(await screen.findByText('Params: id1')).toBeInTheDocument()
   })
+
+  test('when navigating from /posts/$postId to /posts/$postId/info and the current route is /posts/$postId/details', async () => {
+    const rootRoute = createRootRoute()
+
+    const indexRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/',
+      component: () => {
+        return (
+          <React.Fragment>
+            <h1>Index</h1>
+            <Link to="/posts">Posts</Link>
+            <Link to="/posts/$postId/details" params={{ postId: 'id1' }}>
+              To first post
+            </Link>
+          </React.Fragment>
+        )
+      },
+    })
+
+    const layoutRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      id: '_layout',
+      component: () => {
+        return (
+          <>
+            <h1>Layout</h1>
+            <Outlet />
+          </>
+        )
+      },
+    })
+
+    const PostsComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Posts</h1>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postsRoute = createRoute({
+      getParentRoute: () => layoutRoute,
+      path: 'posts',
+      component: PostsComponent,
+    })
+
+    const PostComponent = () => {
+      const params = useParams({ strict: false })
+      return (
+        <React.Fragment>
+          <span>Params: {params.postId}</span>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postRoute = createRoute({
+      getParentRoute: () => postsRoute,
+      path: '$postId',
+      component: PostComponent,
+    })
+
+    const DetailsComponent = () => {
+      return (
+        <>
+          <h1>Details!</h1>
+          <Link from="/posts/$postId" to="/posts/$postId/info">
+            To Information
+          </Link>
+        </>
+      )
+    }
+
+    const detailsRoute = createRoute({
+      getParentRoute: () => postRoute,
+      path: 'details',
+      component: DetailsComponent,
+    })
+
+    const InformationComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Information</h1>
+        </React.Fragment>
+      )
+    }
+
+    const informationRoute = createRoute({
+      getParentRoute: () => postRoute,
+      path: 'info',
+      component: InformationComponent,
+    })
+
+    const router = createRouter({
+      routeTree: rootRoute.addChildren([
+        indexRoute,
+        layoutRoute.addChildren([
+          postsRoute.addChildren([
+            postRoute.addChildren([detailsRoute, informationRoute]),
+          ]),
+        ]),
+      ]),
+    })
+
+    render(<RouterProvider router={router} />)
+
+    const postsLink = await screen.findByRole('link', { name: 'To first post' })
+
+    expect(postsLink).toHaveAttribute('href', '/posts/id1/details')
+
+    fireEvent.click(postsLink)
+
+    expect(await screen.findByText('Params: id1')).toBeInTheDocument()
+
+    expect(window.location.pathname).toEqual('/posts/id1/details')
+
+    const informationLink = await screen.findByRole('link', {
+      name: 'To Information',
+    })
+
+    expect(informationLink).toHaveAttribute('href', '/posts/id1/info')
+
+    fireEvent.click(informationLink)
+
+    expect(await screen.findByText('Information')).toBeInTheDocument()
+
+    expect(window.location.pathname).toEqual('/posts/id1/info')
+
+    expect(await screen.findByText('Params: id1'))
+  })
+
+  test('when navigating from /posts/$postId with a trailing slash to /posts/$postId/info and the current route is /posts/$postId/details', async () => {
+    const rootRoute = createRootRoute()
+
+    const indexRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/',
+      component: () => {
+        return (
+          <React.Fragment>
+            <h1>Index</h1>
+            <Link to="/posts">Posts</Link>
+            <Link to="/posts/$postId/details" params={{ postId: 'id1' }}>
+              To first post
+            </Link>
+          </React.Fragment>
+        )
+      },
+    })
+
+    const layoutRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      id: '_layout',
+      component: () => {
+        return (
+          <>
+            <h1>Layout</h1>
+            <Outlet />
+          </>
+        )
+      },
+    })
+
+    const PostsComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Posts</h1>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postsRoute = createRoute({
+      getParentRoute: () => layoutRoute,
+      path: 'posts',
+      component: PostsComponent,
+    })
+
+    const PostComponent = () => {
+      const params = useParams({ strict: false })
+      return (
+        <React.Fragment>
+          <span>Params: {params.postId}</span>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postRoute = createRoute({
+      getParentRoute: () => postsRoute,
+      path: '$postId/',
+      component: PostComponent,
+    })
+
+    const DetailsComponent = () => {
+      return (
+        <>
+          <h1>Details!</h1>
+          <Link from="/posts/$postId" to="/posts/$postId/info">
+            To Information
+          </Link>
+        </>
+      )
+    }
+
+    const detailsRoute = createRoute({
+      getParentRoute: () => postRoute,
+      path: 'details',
+      component: DetailsComponent,
+    })
+
+    const InformationComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Information</h1>
+        </React.Fragment>
+      )
+    }
+
+    const informationRoute = createRoute({
+      getParentRoute: () => postRoute,
+      path: 'info',
+      component: InformationComponent,
+    })
+
+    const router = createRouter({
+      routeTree: rootRoute.addChildren([
+        indexRoute,
+        layoutRoute.addChildren([
+          postsRoute.addChildren([
+            postRoute.addChildren([detailsRoute, informationRoute]),
+          ]),
+        ]),
+      ]),
+    })
+
+    render(<RouterProvider router={router} />)
+
+    const postsLink = await screen.findByRole('link', { name: 'To first post' })
+
+    expect(postsLink).toHaveAttribute('href', '/posts/id1/details')
+
+    fireEvent.click(postsLink)
+
+    expect(await screen.findByText('Params: id1')).toBeInTheDocument()
+
+    expect(window.location.pathname).toEqual('/posts/id1/details')
+
+    const informationLink = await screen.findByRole('link', {
+      name: 'To Information',
+    })
+
+    expect(informationLink).toHaveAttribute('href', '/posts/id1/info')
+
+    fireEvent.click(informationLink)
+
+    expect(await screen.findByText('Information')).toBeInTheDocument()
+
+    expect(window.location.pathname).toEqual('/posts/id1/info')
+
+    expect(await screen.findByText('Params: id1'))
+  })
+
+  test('when navigating from /posts/$postId to ./info and the current route is /posts/$postId/details', async () => {
+    const rootRoute = createRootRoute()
+
+    const indexRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/',
+      component: () => {
+        return (
+          <React.Fragment>
+            <h1>Index</h1>
+            <Link to="/posts">Posts</Link>
+            <Link to="/posts/$postId/details" params={{ postId: 'id1' }}>
+              To first post
+            </Link>
+          </React.Fragment>
+        )
+      },
+    })
+
+    const layoutRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      id: '_layout',
+      component: () => {
+        return (
+          <>
+            <h1>Layout</h1>
+            <Outlet />
+          </>
+        )
+      },
+    })
+
+    const PostsComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Posts</h1>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postsRoute = createRoute({
+      getParentRoute: () => layoutRoute,
+      path: 'posts',
+      component: PostsComponent,
+    })
+
+    const PostComponent = () => {
+      const params = useParams({ strict: false })
+      return (
+        <React.Fragment>
+          <span>Params: {params.postId}</span>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postRoute = createRoute({
+      getParentRoute: () => postsRoute,
+      path: '$postId',
+      component: PostComponent,
+    })
+
+    const DetailsComponent = () => {
+      return (
+        <>
+          <h1>Details!</h1>
+          <Link from="/posts/$postId" to="./info">
+            To Information
+          </Link>
+        </>
+      )
+    }
+
+    const detailsRoute = createRoute({
+      getParentRoute: () => postRoute,
+      path: 'details',
+      component: DetailsComponent,
+    })
+
+    const InformationComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Information</h1>
+        </React.Fragment>
+      )
+    }
+
+    const informationRoute = createRoute({
+      getParentRoute: () => postRoute,
+      path: 'info',
+      component: InformationComponent,
+    })
+
+    const router = createRouter({
+      routeTree: rootRoute.addChildren([
+        indexRoute,
+        layoutRoute.addChildren([
+          postsRoute.addChildren([
+            postRoute.addChildren([detailsRoute, informationRoute]),
+          ]),
+        ]),
+      ]),
+    })
+
+    render(<RouterProvider router={router} />)
+
+    const postsLink = await screen.findByRole('link', { name: 'To first post' })
+
+    expect(postsLink).toHaveAttribute('href', '/posts/id1/details')
+
+    fireEvent.click(postsLink)
+
+    expect(await screen.findByText('Params: id1')).toBeInTheDocument()
+
+    expect(window.location.pathname).toEqual('/posts/id1/details')
+
+    const informationLink = await screen.findByRole('link', {
+      name: 'To Information',
+    })
+
+    expect(informationLink).toHaveAttribute('href', '/posts/id1/info')
+
+    fireEvent.click(informationLink)
+
+    expect(await screen.findByText('Information')).toBeInTheDocument()
+
+    expect(window.location.pathname).toEqual('/posts/id1/info')
+
+    expect(await screen.findByText('Params: id1'))
+  })
+
+  test('when navigating from /posts/$postId with search and to ./info with search and the current route is /posts/$postId/details', async () => {
+    const rootRoute = createRootRoute()
+
+    const indexRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/',
+      component: () => {
+        return (
+          <React.Fragment>
+            <h1>Index</h1>
+            <Link to="/posts">Posts</Link>
+            <Link
+              to="/posts/$postId/details"
+              params={{ postId: 'id1' }}
+              search={{ page: 2 }}
+            >
+              To first post
+            </Link>
+          </React.Fragment>
+        )
+      },
+    })
+
+    const layoutRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      id: '_layout',
+      component: () => {
+        return (
+          <>
+            <h1>Layout</h1>
+            <Outlet />
+          </>
+        )
+      },
+    })
+
+    const PostsComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Posts</h1>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postsRoute = createRoute({
+      getParentRoute: () => layoutRoute,
+      path: 'posts',
+      component: PostsComponent,
+      validateSearch: () => ({ page: 2 }),
+    })
+
+    const PostComponent = () => {
+      const params = useParams({ strict: false })
+      return (
+        <React.Fragment>
+          <span>Params: {params.postId}</span>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postRoute = createRoute({
+      getParentRoute: () => postsRoute,
+      path: '$postId',
+      component: PostComponent,
+    })
+
+    const DetailsComponent = () => {
+      return (
+        <>
+          <h1>Details!</h1>
+          <Link
+            from="/posts/$postId"
+            to="./info"
+            search={(prev: any): any => ({ ...prev, more: true })}
+          >
+            To Information
+          </Link>
+        </>
+      )
+    }
+
+    const detailsRoute = createRoute({
+      getParentRoute: () => postRoute,
+      path: 'details',
+      component: DetailsComponent,
+    })
+
+    const InformationComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Information</h1>
+        </React.Fragment>
+      )
+    }
+
+    const informationRoute = createRoute({
+      getParentRoute: () => postRoute,
+      path: 'info',
+      component: InformationComponent,
+      validateSearch: () => ({ more: false }),
+    })
+
+    const router = createRouter({
+      routeTree: rootRoute.addChildren([
+        indexRoute,
+        layoutRoute.addChildren([
+          postsRoute.addChildren([
+            postRoute.addChildren([detailsRoute, informationRoute]),
+          ]),
+        ]),
+      ]),
+    })
+
+    render(<RouterProvider router={router} />)
+
+    const postsLink = await screen.findByRole('link', { name: 'To first post' })
+
+    expect(postsLink).toHaveAttribute('href', '/posts/id1/details?page=2')
+
+    fireEvent.click(postsLink)
+
+    expect(await screen.findByText('Params: id1')).toBeInTheDocument()
+
+    expect(window.location.pathname).toEqual('/posts/id1/details')
+
+    const informationLink = await screen.findByRole('link', {
+      name: 'To Information',
+    })
+
+    expect(informationLink).toHaveAttribute(
+      'href',
+      '/posts/id1/info?page=2&more=true',
+    )
+
+    fireEvent.click(informationLink)
+
+    expect(await screen.findByText('Information')).toBeInTheDocument()
+
+    expect(window.location.pathname).toEqual('/posts/id1/info')
+
+    expect(await screen.findByText('Params: id1'))
+  })
+
+  test('when navigating from /posts/$postId to ../$postId and the current route is /posts/$postId/details', async () => {
+    const rootRoute = createRootRoute()
+
+    const indexRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/',
+      component: () => {
+        return (
+          <React.Fragment>
+            <h1>Index</h1>
+            <Link to="/posts">Posts</Link>
+            <Link to="/posts/$postId/details" params={{ postId: 'id1' }}>
+              To first post
+            </Link>
+          </React.Fragment>
+        )
+      },
+    })
+
+    const layoutRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      id: '_layout',
+      component: () => {
+        return (
+          <>
+            <h1>Layout</h1>
+            <Outlet />
+          </>
+        )
+      },
+    })
+
+    const PostsComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Posts</h1>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postsRoute = createRoute({
+      getParentRoute: () => layoutRoute,
+      path: 'posts',
+      component: PostsComponent,
+    })
+
+    const PostComponent = () => {
+      const params = useParams({ strict: false })
+      return (
+        <React.Fragment>
+          <span>Params: {params.postId}</span>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postRoute = createRoute({
+      getParentRoute: () => postsRoute,
+      path: '$postId',
+      component: PostComponent,
+    })
+
+    const DetailsComponent = () => {
+      return (
+        <>
+          <h1>Details!</h1>
+          <Link from="/posts/$postId" to="../$postId">
+            To Post
+          </Link>
+        </>
+      )
+    }
+
+    const detailsRoute = createRoute({
+      getParentRoute: () => postRoute,
+      path: 'details',
+      component: DetailsComponent,
+    })
+
+    const InformationComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Information</h1>
+        </React.Fragment>
+      )
+    }
+
+    const informationRoute = createRoute({
+      getParentRoute: () => postRoute,
+      path: 'info',
+      component: InformationComponent,
+    })
+
+    const router = createRouter({
+      routeTree: rootRoute.addChildren([
+        indexRoute,
+        layoutRoute.addChildren([
+          postsRoute.addChildren([
+            postRoute.addChildren([detailsRoute, informationRoute]),
+          ]),
+        ]),
+      ]),
+    })
+
+    render(<RouterProvider router={router} />)
+
+    const postsLink = await screen.findByRole('link', { name: 'To first post' })
+
+    expect(postsLink).toHaveAttribute('href', '/posts/id1/details')
+
+    fireEvent.click(postsLink)
+
+    expect(await screen.findByText('Params: id1')).toBeInTheDocument()
+
+    expect(window.location.pathname).toEqual('/posts/id1/details')
+
+    const postLink = await screen.findByRole('link', {
+      name: 'To Post',
+    })
+
+    expect(postLink).toHaveAttribute('href', '/posts/id1')
+
+    fireEvent.click(postLink)
+
+    expect(await screen.findByText('Posts')).toBeInTheDocument()
+
+    expect(window.location.pathname).toEqual('/posts/id1')
+  })
+
+  test('when navigating from /posts/$postId with an index to ../$postId and the current route is /posts/$postId/details', async () => {
+    const rootRoute = createRootRoute()
+
+    const indexRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/',
+      component: () => {
+        return (
+          <React.Fragment>
+            <h1>Index</h1>
+            <Link to="/posts">Posts</Link>
+            <Link to="/posts/$postId/details" params={{ postId: 'id1' }}>
+              To first post
+            </Link>
+          </React.Fragment>
+        )
+      },
+    })
+
+    const layoutRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      id: '_layout',
+      component: () => {
+        return (
+          <>
+            <h1>Layout</h1>
+            <Outlet />
+          </>
+        )
+      },
+    })
+
+    const PostsComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Posts</h1>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postsRoute = createRoute({
+      getParentRoute: () => layoutRoute,
+      path: 'posts',
+      component: PostsComponent,
+    })
+
+    const PostComponent = () => {
+      const params = useParams({ strict: false })
+      return (
+        <React.Fragment>
+          <span>Params: {params.postId}</span>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postRoute = createRoute({
+      getParentRoute: () => postsRoute,
+      path: '$postId',
+      component: PostComponent,
+    })
+
+    const postIndexRoute = createRoute({
+      getParentRoute: () => postRoute,
+      path: '/',
+      component: PostComponent,
+    })
+
+    const DetailsComponent = () => {
+      return (
+        <>
+          <h1>Details!</h1>
+          <Link from="/posts/$postId" to="../$postId">
+            To Post
+          </Link>
+        </>
+      )
+    }
+
+    const detailsRoute = createRoute({
+      getParentRoute: () => postRoute,
+      path: 'details',
+      component: DetailsComponent,
+    })
+
+    const InformationComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Information</h1>
+        </React.Fragment>
+      )
+    }
+
+    const informationRoute = createRoute({
+      getParentRoute: () => postRoute,
+      path: 'info',
+      component: InformationComponent,
+    })
+
+    const router = createRouter({
+      routeTree: rootRoute.addChildren([
+        indexRoute,
+        layoutRoute.addChildren([
+          postsRoute.addChildren([
+            postRoute.addChildren([
+              postIndexRoute,
+              detailsRoute,
+              informationRoute,
+            ]),
+          ]),
+        ]),
+      ]),
+    })
+
+    render(<RouterProvider router={router} />)
+
+    const postsLink = await screen.findByRole('link', { name: 'To first post' })
+
+    expect(postsLink).toHaveAttribute('href', '/posts/id1/details')
+
+    fireEvent.click(postsLink)
+
+    expect(await screen.findByText('Params: id1')).toBeInTheDocument()
+
+    expect(window.location.pathname).toEqual('/posts/id1/details')
+
+    const postLink = await screen.findByRole('link', {
+      name: 'To Post',
+    })
+
+    expect(postLink).toHaveAttribute('href', '/posts/id1')
+
+    fireEvent.click(postLink)
+
+    expect(await screen.findByText('Posts')).toBeInTheDocument()
+
+    expect(window.location.pathname).toEqual('/posts/id1')
+  })
+
+  test('when navigating from /invoices to ./invoiceId and the current route is /posts/$postId/details', async () => {
+    const rootRoute = createRootRoute()
+
+    const indexRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/',
+      component: () => {
+        return (
+          <React.Fragment>
+            <h1>Index</h1>
+            <Link to="/posts">Posts</Link>
+            <Link to="/posts/$postId/details" params={{ postId: 'id1' }}>
+              To first post
+            </Link>
+          </React.Fragment>
+        )
+      },
+    })
+
+    const layoutRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      id: '_layout',
+      component: () => {
+        return (
+          <>
+            <h1>Layout</h1>
+            <Outlet />
+          </>
+        )
+      },
+    })
+
+    const PostsComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Posts</h1>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postsRoute = createRoute({
+      getParentRoute: () => layoutRoute,
+      path: 'posts',
+      component: PostsComponent,
+    })
+
+    const PostComponent = () => {
+      const params = useParams({ strict: false })
+      return (
+        <React.Fragment>
+          <span>Params: {params.postId}</span>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postRoute = createRoute({
+      getParentRoute: () => postsRoute,
+      path: '$postId',
+      component: PostComponent,
+    })
+
+    const DetailsComponent = () => {
+      return (
+        <>
+          <h1>Details!</h1>
+          <Link
+            from="/invoices"
+            to="./$invoiceId"
+            params={{ invoiceId: 'id1' }}
+          >
+            To Invoices
+          </Link>
+        </>
+      )
+    }
+
+    const detailsRoute = createRoute({
+      getParentRoute: () => postRoute,
+      path: 'details',
+      component: DetailsComponent,
+    })
+
+    const InformationComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Information</h1>
+        </React.Fragment>
+      )
+    }
+
+    const informationRoute = createRoute({
+      getParentRoute: () => postRoute,
+      path: 'info',
+      component: InformationComponent,
+    })
+
+    const invoicesRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: 'invoices',
+      component: () => (
+        <>
+          <h1>Invoices!</h1>
+          <Outlet />
+        </>
+      ),
+    })
+
+    const InvoiceComponent = () => {
+      const params = useParams({ strict: false })
+      return (
+        <>
+          <span>invoiceId: {params.invoiceId}</span>
+        </>
+      )
+    }
+
+    const invoiceRoute = createRoute({
+      getParentRoute: () => invoicesRoute,
+      path: '$invoiceId',
+      component: InvoiceComponent,
+    })
+
+    const router = createRouter({
+      routeTree: rootRoute.addChildren([
+        indexRoute,
+        layoutRoute.addChildren([
+          invoicesRoute.addChildren([invoiceRoute]),
+          postsRoute.addChildren([
+            postRoute.addChildren([detailsRoute, informationRoute]),
+          ]),
+        ]),
+      ]),
+    })
+
+    render(<RouterProvider router={router} />)
+
+    const postsLink = await screen.findByRole('link', { name: 'To first post' })
+
+    expect(postsLink).toHaveAttribute('href', '/posts/id1/details')
+
+    fireEvent.click(postsLink)
+
+    expect(
+      await screen.findByText(
+        'Invariant failed: Could not find match for from: /invoices',
+      ),
+    ).toBeInTheDocument()
+  })
+
+  test('when navigating to /posts/$postId/info which is declaratively masked as /posts/$postId', async () => {
+    const rootRoute = createRootRoute()
+
+    const indexRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/',
+      component: () => {
+        return (
+          <React.Fragment>
+            <h1>Index</h1>
+            <Link to="/posts/$postId/info" params={{ postId: 'id1' }}>
+              To first post
+            </Link>
+          </React.Fragment>
+        )
+      },
+    })
+
+    const PostsComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Posts</h1>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postsRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: 'posts',
+      component: PostsComponent,
+    })
+
+    const PostComponent = () => {
+      const params = useParams({ strict: false })
+      return (
+        <React.Fragment>
+          <span>Params: {params.postId}</span>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postRoute = createRoute({
+      getParentRoute: () => postsRoute,
+      path: '$postId',
+      component: PostComponent,
+    })
+
+    const InformationComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Information</h1>
+        </React.Fragment>
+      )
+    }
+
+    const informationRoute = createRoute({
+      getParentRoute: () => postRoute,
+      path: 'info',
+      component: InformationComponent,
+    })
+
+    const routeTree = rootRoute.addChildren([
+      indexRoute,
+      postsRoute.addChildren([postRoute.addChildren([informationRoute])]),
+    ])
+
+    const routeMask = createRouteMask({
+      routeTree,
+      from: '/posts/$postId/info',
+      to: '/posts/$postId',
+    })
+
+    const router = createRouter({
+      routeTree,
+      routeMasks: [routeMask],
+    })
+
+    render(<RouterProvider router={router} />)
+
+    const informationLink = await screen.findByRole('link', {
+      name: 'To first post',
+    })
+
+    expect(informationLink).toHaveAttribute('href', '/posts/id1')
+  })
+
+  test('when navigating to /posts/$postId/info which is imperatively masked as /posts/$postId', async () => {
+    const rootRoute = createRootRoute()
+
+    const indexRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/',
+      component: () => {
+        return (
+          <React.Fragment>
+            <h1>Index</h1>
+            <Link
+              to="/posts/$postId/info"
+              params={{ postId: 'id1' }}
+              mask={{ to: '/posts/$postId', params: { postId: 'id1' } }}
+            >
+              To first post
+            </Link>
+          </React.Fragment>
+        )
+      },
+    })
+
+    const PostsComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Posts</h1>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postsRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: 'posts',
+      component: PostsComponent,
+    })
+
+    const PostComponent = () => {
+      const params = useParams({ strict: false })
+      return (
+        <React.Fragment>
+          <span>Params: {params.postId}</span>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postRoute = createRoute({
+      getParentRoute: () => postsRoute,
+      path: '$postId',
+      component: PostComponent,
+    })
+
+    const InformationComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Information</h1>
+        </React.Fragment>
+      )
+    }
+
+    const informationRoute = createRoute({
+      getParentRoute: () => postRoute,
+      path: 'info',
+      component: InformationComponent,
+    })
+
+    const routeTree = rootRoute.addChildren([
+      indexRoute,
+      postsRoute.addChildren([postRoute.addChildren([informationRoute])]),
+    ])
+
+    const router = createRouter({
+      routeTree,
+    })
+
+    render(<RouterProvider router={router} />)
+
+    const informationLink = await screen.findByRole('link', {
+      name: 'To first post',
+    })
+
+    expect(informationLink).toHaveAttribute('href', '/posts/id1')
+  })
+
+  test('when preloading /post/$postId with a redirects to /login', async () => {
+    const rootRoute = createRootRoute()
+
+    const indexRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/',
+      component: () => {
+        return (
+          <React.Fragment>
+            <h1>Index</h1>
+            <Link to="/posts/$postId" params={{ postId: 'id1' }}>
+              To first post
+            </Link>
+          </React.Fragment>
+        )
+      },
+    })
+
+    const PostsComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Posts</h1>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const loaderFn = vi.fn()
+
+    const postsRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: 'posts',
+      component: PostsComponent,
+    })
+
+    const PostComponent = () => {
+      const params = useParams({ strict: false })
+      return (
+        <React.Fragment>
+          <span>Params: {params.postId}</span>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const search = vi.fn((prev) => ({ page: prev.postPage }))
+
+    const postRoute = createRoute({
+      getParentRoute: () => postsRoute,
+      path: '$postId',
+      component: PostComponent,
+      validateSearch: () => ({ postPage: 0 }),
+      loader: () => {
+        loaderFn()
+        throw redirect({
+          to: '/login',
+          search,
+        })
+      },
+    })
+
+    const LoginComponent = () => {
+      const [status, setStatus] = React.useState<'idle' | 'success' | 'error'>(
+        'idle',
+      )
+
+      React.useEffect(() => {
+        const onLoad = async () => {
+          try {
+            await router.preloadRoute({
+              to: '/posts/$postId',
+              params: { postId: 'id1' },
+              search: { postPage: 0 },
+            })
+            setStatus('success')
+          } catch (e) {
+            setStatus('error')
+          }
+        }
+        onLoad()
+      }, [])
+
+      return (
+        <React.Fragment>
+          {status === 'success' ? 'Login!' : 'Waiting...'}
+        </React.Fragment>
+      )
+    }
+
+    const loginRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: 'login',
+      component: LoginComponent,
+      validateSearch: () => ({ page: 0 }),
+    })
+
+    const routeTree = rootRoute.addChildren([
+      indexRoute,
+      loginRoute,
+      postsRoute.addChildren([postRoute]),
+    ])
+
+    const router = createRouter({
+      routeTree,
+      defaultPreload: 'intent',
+    })
+
+    render(<RouterProvider router={router} />)
+
+    const postLink = await screen.findByRole('link', {
+      name: 'To first post',
+    })
+
+    expect(postLink).toHaveAttribute('href', '/posts/id1')
+
+    fireEvent.mouseOver(postLink)
+
+    await waitFor(() => expect(loaderFn).toHaveBeenCalled())
+
+    expect(search).toHaveBeenCalledWith({ postPage: 0 })
+
+    fireEvent.click(postLink)
+
+    expect(await screen.findByText('Login!')).toBeInTheDocument()
+  })
+
+  test('when preloading /post/$postId with a beforeLoad that navigates to /login', async () => {
+    const rootRoute = createRootRoute()
+
+    const indexRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/',
+      component: () => {
+        return (
+          <React.Fragment>
+            <h1>Index</h1>
+            <Link to="/posts/$postId" params={{ postId: 'id1' }}>
+              To first post
+            </Link>
+          </React.Fragment>
+        )
+      },
+    })
+
+    const PostsComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Posts</h1>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postsRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: 'posts',
+      component: PostsComponent,
+    })
+
+    const PostComponent = () => {
+      const params = useParams({ strict: false })
+      return (
+        <React.Fragment>
+          <span>Params: {params.postId}</span>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const search = vi.fn((prev) => ({ page: prev.postPage }))
+
+    const postRoute = createRoute({
+      getParentRoute: () => postsRoute,
+      path: '$postId',
+      component: PostComponent,
+      validateSearch: () => ({ postPage: 0 }),
+      beforeLoad: (context) => context.navigate({ to: '/login', search }),
+    })
+
+    const LoginComponent = () => {
+      return <React.Fragment>Login!</React.Fragment>
+    }
+
+    const loginRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: 'login',
+      component: LoginComponent,
+      validateSearch: () => ({ page: 0 }),
+    })
+
+    const routeTree = rootRoute.addChildren([
+      indexRoute,
+      loginRoute,
+      postsRoute.addChildren([postRoute]),
+    ])
+
+    const router = createRouter({
+      routeTree,
+      defaultPreload: 'intent',
+    })
+
+    render(<RouterProvider router={router} />)
+
+    const postLink = await screen.findByRole('link', {
+      name: 'To first post',
+    })
+
+    expect(postLink).toHaveAttribute('href', '/posts/id1')
+
+    fireEvent.mouseOver(postLink)
+
+    await waitFor(() => expect(search).toHaveBeenCalledWith({ postPage: 0 }))
+
+    fireEvent.click(postLink)
+
+    expect(await screen.findByText('Login!')).toBeInTheDocument()
+  })
+
+  test('when preloading /post/$postId with a loader that navigates to /login', async () => {
+    const rootRoute = createRootRoute()
+
+    const indexRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: '/',
+      component: () => {
+        return (
+          <React.Fragment>
+            <h1>Index</h1>
+            <Link to="/posts/$postId" params={{ postId: 'id1' }}>
+              To first post
+            </Link>
+          </React.Fragment>
+        )
+      },
+    })
+
+    const PostsComponent = () => {
+      return (
+        <React.Fragment>
+          <h1>Posts</h1>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const postsRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: 'posts',
+      component: PostsComponent,
+    })
+
+    const PostComponent = () => {
+      const params = useParams({ strict: false })
+      return (
+        <React.Fragment>
+          <span>Params: {params.postId}</span>
+          <Outlet />
+        </React.Fragment>
+      )
+    }
+
+    const search = vi.fn((prev) => ({ page: prev.postPage }))
+
+    const postRoute = createRoute({
+      getParentRoute: () => postsRoute,
+      path: '$postId',
+      component: PostComponent,
+      validateSearch: () => ({ postPage: 0 }),
+      loader: (context) => {
+        context.navigate({ to: '/login', search })
+      },
+    })
+
+    const LoginComponent = () => {
+      return <React.Fragment>Login!</React.Fragment>
+    }
+
+    const loginRoute = createRoute({
+      getParentRoute: () => rootRoute,
+      path: 'login',
+      component: LoginComponent,
+      validateSearch: () => ({ page: 0 }),
+    })
+
+    const routeTree = rootRoute.addChildren([
+      indexRoute,
+      loginRoute,
+      postsRoute.addChildren([postRoute]),
+    ])
+
+    const router = createRouter({
+      routeTree,
+      defaultPreload: 'intent',
+    })
+
+    render(<RouterProvider router={router} />)
+
+    const postLink = await screen.findByRole('link', {
+      name: 'To first post',
+    })
+
+    expect(postLink).toHaveAttribute('href', '/posts/id1')
+
+    fireEvent.mouseOver(postLink)
+
+    await waitFor(() => expect(search).toHaveBeenCalledWith({ postPage: 0 }))
+
+    fireEvent.click(postLink)
+
+    expect(await screen.findByText('Login!')).toBeInTheDocument()
+  })
 })
 
 describe('createLink', () => {
diff --git a/packages/react-router/tests/useNavigate.test.tsx b/packages/react-router/tests/useNavigate.test.tsx
new file mode 100644
index 000000000..ec6bbb5d6
--- /dev/null
+++ b/packages/react-router/tests/useNavigate.test.tsx
@@ -0,0 +1,1233 @@
+import React from 'react'
+import '@testing-library/jest-dom/vitest'
+import { afterEach, expect, test } from 'vitest'
+import { cleanup, fireEvent, render, screen } from '@testing-library/react'
+
+import {
+  createRootRoute,
+  createRoute,
+  createRouter,
+  useParams,
+  Outlet,
+  useNavigate,
+  RouterProvider,
+  createRouteMask,
+} from '../src'
+
+afterEach(() => {
+  window.history.replaceState(null, 'root', '/')
+  cleanup()
+})
+
+test('when navigating to /posts', async () => {
+  const rootRoute = createRootRoute()
+
+  const IndexComponent = () => {
+    const navigate = useNavigate()
+    return (
+      <React.Fragment>
+        <h1>Index</h1>
+        <button onClick={() => navigate({ to: '/' })}>Index</button>
+        <button onClick={() => navigate({ to: '/posts' })}>Posts</button>
+      </React.Fragment>
+    )
+  }
+
+  const indexRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/',
+    component: IndexComponent,
+  })
+
+  const postsRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/posts',
+    component: () => {
+      return (
+        <React.Fragment>
+          <h1>Posts</h1>
+        </React.Fragment>
+      )
+    },
+  })
+
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([indexRoute, postsRoute]),
+  })
+
+  render(<RouterProvider router={router} />)
+
+  const postsButton = await screen.findByRole('button', { name: 'Posts' })
+
+  fireEvent.click(postsButton)
+
+  expect(
+    await screen.findByRole('heading', { name: 'Posts' }),
+  ).toBeInTheDocument()
+
+  expect(window.location.pathname).toBe('/posts')
+})
+
+test('when navigating from /posts to ./$postId', async () => {
+  const rootRoute = createRootRoute()
+  const IndexComponent = () => {
+    const navigate = useNavigate()
+    return (
+      <React.Fragment>
+        <h1>Index</h1>
+        <button onClick={() => navigate({ to: '/posts' })}>Posts</button>
+        <button
+          onClick={() =>
+            navigate({ to: '/posts/$postId', params: { postId: 'id1' } })
+          }
+        >
+          To first post
+        </button>
+      </React.Fragment>
+    )
+  }
+
+  const indexRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/',
+    component: IndexComponent,
+  })
+
+  const PostsComponent = () => {
+    return (
+      <React.Fragment>
+        <h1>Posts</h1>
+        <Outlet />
+      </React.Fragment>
+    )
+  }
+
+  const postsRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: 'posts',
+    component: PostsComponent,
+  })
+
+  const PostsIndexComponent = () => {
+    const navigate = useNavigate()
+    return (
+      <React.Fragment>
+        <h1>Posts Index</h1>
+        <button
+          onClick={() =>
+            navigate({
+              from: '/posts/',
+              to: './$postId',
+              params: { postId: 'id1' },
+            })
+          }
+        >
+          To the first post
+        </button>
+      </React.Fragment>
+    )
+  }
+
+  const postsIndexRoute = createRoute({
+    getParentRoute: () => postsRoute,
+    path: '/',
+    component: PostsIndexComponent,
+  })
+
+  const PostComponent = () => {
+    const params = useParams({ strict: false })
+    const navigate = useNavigate()
+    return (
+      <React.Fragment>
+        <span>Params: {params.postId}</span>
+        <button onClick={() => navigate({ to: '/' })}>Index</button>
+      </React.Fragment>
+    )
+  }
+
+  const postRoute = createRoute({
+    getParentRoute: () => postsRoute,
+    path: '$postId',
+    component: PostComponent,
+  })
+
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([
+      indexRoute,
+      postsRoute.addChildren([postsIndexRoute, postRoute]),
+    ]),
+  })
+
+  render(<RouterProvider router={router} />)
+
+  const postsButton = await screen.findByRole('button', { name: 'Posts' })
+
+  fireEvent.click(postsButton)
+
+  expect(await screen.findByText('Posts Index')).toBeInTheDocument()
+
+  const postButton = await screen.findByRole('button', {
+    name: 'To the first post',
+  })
+
+  fireEvent.click(postButton)
+
+  expect(await screen.findByText('Params: id1')).toBeInTheDocument()
+
+  expect(window.location.pathname).toBe('/posts/id1')
+})
+
+test('when navigating from /posts to ../posts/$postId', async () => {
+  const rootRoute = createRootRoute()
+
+  const IndexComponent = () => {
+    const navigate = useNavigate()
+    return (
+      <React.Fragment>
+        <h1>Index</h1>
+        <button onClick={() => navigate({ to: '/posts' })}>Posts</button>
+        <button
+          onClick={() =>
+            navigate({ to: '/posts/$postId', params: { postId: 'id1' } })
+          }
+        >
+          To first post
+        </button>
+      </React.Fragment>
+    )
+  }
+
+  const indexRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/',
+    component: IndexComponent,
+  })
+
+  const PostsComponent = () => {
+    return (
+      <React.Fragment>
+        <h1>Posts</h1>
+        <Outlet />
+      </React.Fragment>
+    )
+  }
+
+  const postsRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: 'posts',
+    component: PostsComponent,
+  })
+
+  const PostsIndexComponent = () => {
+    const navigate = useNavigate()
+    return (
+      <React.Fragment>
+        <h1>Posts Index</h1>
+        <button
+          onClick={() =>
+            navigate({
+              from: '/posts/',
+              to: '../posts/$postId',
+              params: { postId: 'id1' },
+            })
+          }
+        >
+          To the first post
+        </button>
+      </React.Fragment>
+    )
+  }
+
+  const postsIndexRoute = createRoute({
+    getParentRoute: () => postsRoute,
+    path: '/',
+    component: PostsIndexComponent,
+  })
+
+  const PostComponent = () => {
+    const navigate = useNavigate()
+    const params = useParams({ strict: false })
+    return (
+      <React.Fragment>
+        <span>Params: {params.postId}</span>
+        <button onClick={() => navigate({ to: '/' })}>Index</button>
+      </React.Fragment>
+    )
+  }
+
+  const postRoute = createRoute({
+    getParentRoute: () => postsRoute,
+    path: '$postId',
+    component: PostComponent,
+  })
+
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([
+      indexRoute,
+      postsRoute.addChildren([postsIndexRoute, postRoute]),
+    ]),
+  })
+
+  render(<RouterProvider router={router} />)
+
+  const postsButton = await screen.findByRole('button', { name: 'Posts' })
+
+  fireEvent.click(postsButton)
+
+  expect(await screen.findByText('Posts Index')).toBeInTheDocument()
+
+  const postButton = await screen.findByRole('button', {
+    name: 'To the first post',
+  })
+
+  fireEvent.click(postButton)
+
+  expect(await screen.findByText('Params: id1')).toBeInTheDocument()
+})
+
+test('when navigating from /posts/$postId to /posts/$postId/info and the current route is /posts/$postId/details', async () => {
+  const rootRoute = createRootRoute()
+
+  const IndexComponent = () => {
+    const navigate = useNavigate()
+    return (
+      <React.Fragment>
+        <h1>Index</h1>
+        <button onClick={() => navigate({ to: '/posts' })}>Posts</button>
+        <button
+          onClick={() =>
+            navigate({
+              to: '/posts/$postId/details',
+              params: { postId: 'id1' },
+            })
+          }
+        >
+          To first post
+        </button>
+      </React.Fragment>
+    )
+  }
+
+  const indexRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/',
+    component: IndexComponent,
+  })
+
+  const layoutRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    id: '_layout',
+    component: () => {
+      return (
+        <>
+          <h1>Layout</h1>
+          <Outlet />
+        </>
+      )
+    },
+  })
+
+  const PostsComponent = () => {
+    return (
+      <React.Fragment>
+        <h1>Posts</h1>
+        <Outlet />
+      </React.Fragment>
+    )
+  }
+
+  const postsRoute = createRoute({
+    getParentRoute: () => layoutRoute,
+    path: 'posts',
+    component: PostsComponent,
+  })
+
+  const PostComponent = () => {
+    const params = useParams({ strict: false })
+    return (
+      <React.Fragment>
+        <span>Params: {params.postId}</span>
+        <Outlet />
+      </React.Fragment>
+    )
+  }
+
+  const postRoute = createRoute({
+    getParentRoute: () => postsRoute,
+    path: '$postId',
+    component: PostComponent,
+  })
+
+  const DetailsComponent = () => {
+    const navigate = useNavigate()
+    return (
+      <>
+        <h1>Details!</h1>
+        <button
+          onClick={() =>
+            navigate({ from: '/posts/$postId', to: '/posts/$postId/info' })
+          }
+        >
+          To Information
+        </button>
+      </>
+    )
+  }
+
+  const detailsRoute = createRoute({
+    getParentRoute: () => postRoute,
+    path: 'details',
+    component: DetailsComponent,
+  })
+
+  const InformationComponent = () => {
+    return (
+      <React.Fragment>
+        <h1>Information</h1>
+      </React.Fragment>
+    )
+  }
+
+  const informationRoute = createRoute({
+    getParentRoute: () => postRoute,
+    path: 'info',
+    component: InformationComponent,
+  })
+
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([
+      indexRoute,
+      layoutRoute.addChildren([
+        postsRoute.addChildren([
+          postRoute.addChildren([detailsRoute, informationRoute]),
+        ]),
+      ]),
+    ]),
+  })
+
+  render(<RouterProvider router={router} />)
+
+  const postsButton = await screen.findByRole('button', {
+    name: 'To first post',
+  })
+
+  fireEvent.click(postsButton)
+
+  expect(await screen.findByText('Params: id1')).toBeInTheDocument()
+
+  expect(window.location.pathname).toEqual('/posts/id1/details')
+
+  const informationButton = await screen.findByRole('button', {
+    name: 'To Information',
+  })
+
+  fireEvent.click(informationButton)
+
+  expect(await screen.findByText('Information')).toBeInTheDocument()
+
+  expect(window.location.pathname).toEqual('/posts/id1/info')
+
+  expect(await screen.findByText('Params: id1'))
+})
+
+test('when navigating from /posts/$postId to ./info and the current route is /posts/$postId/details', async () => {
+  const rootRoute = createRootRoute()
+
+  const IndexComponent = () => {
+    const navigate = useNavigate()
+    return (
+      <React.Fragment>
+        <h1>Index</h1>
+        <button onClick={() => navigate({ to: '/posts' })}>Posts</button>
+        <button
+          onClick={() =>
+            navigate({
+              to: '/posts/$postId/details',
+              params: { postId: 'id1' },
+            })
+          }
+        >
+          To first post
+        </button>
+      </React.Fragment>
+    )
+  }
+
+  const indexRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/',
+    component: IndexComponent,
+  })
+
+  const layoutRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    id: '_layout',
+    component: () => {
+      return (
+        <>
+          <h1>Layout</h1>
+          <Outlet />
+        </>
+      )
+    },
+  })
+
+  const PostsComponent = () => {
+    return (
+      <React.Fragment>
+        <h1>Posts</h1>
+        <Outlet />
+      </React.Fragment>
+    )
+  }
+
+  const postsRoute = createRoute({
+    getParentRoute: () => layoutRoute,
+    path: 'posts',
+    component: PostsComponent,
+  })
+
+  const PostComponent = () => {
+    const params = useParams({ strict: false })
+    return (
+      <React.Fragment>
+        <span>Params: {params.postId}</span>
+        <Outlet />
+      </React.Fragment>
+    )
+  }
+
+  const postRoute = createRoute({
+    getParentRoute: () => postsRoute,
+    path: '$postId',
+    component: PostComponent,
+  })
+
+  const DetailsComponent = () => {
+    const navigate = useNavigate()
+    return (
+      <>
+        <h1>Details!</h1>
+        <button
+          onClick={() => navigate({ from: '/posts/$postId', to: './info' })}
+        >
+          To Information
+        </button>
+      </>
+    )
+  }
+
+  const detailsRoute = createRoute({
+    getParentRoute: () => postRoute,
+    path: 'details',
+    component: DetailsComponent,
+  })
+
+  const InformationComponent = () => {
+    return (
+      <React.Fragment>
+        <h1>Information</h1>
+      </React.Fragment>
+    )
+  }
+
+  const informationRoute = createRoute({
+    getParentRoute: () => postRoute,
+    path: 'info',
+    component: InformationComponent,
+  })
+
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([
+      indexRoute,
+      layoutRoute.addChildren([
+        postsRoute.addChildren([
+          postRoute.addChildren([detailsRoute, informationRoute]),
+        ]),
+      ]),
+    ]),
+  })
+
+  render(<RouterProvider router={router} />)
+
+  const postsButton = await screen.findByRole('button', {
+    name: 'To first post',
+  })
+
+  fireEvent.click(postsButton)
+
+  expect(await screen.findByText('Params: id1')).toBeInTheDocument()
+
+  expect(window.location.pathname).toEqual('/posts/id1/details')
+
+  const informationButton = await screen.findByRole('button', {
+    name: 'To Information',
+  })
+
+  fireEvent.click(informationButton)
+
+  expect(await screen.findByText('Information')).toBeInTheDocument()
+
+  expect(window.location.pathname).toEqual('/posts/id1/info')
+
+  expect(await screen.findByText('Params: id1'))
+})
+
+test('when navigating from /posts/$postId to ../$postId and the current route is /posts/$postId/details', async () => {
+  const rootRoute = createRootRoute()
+
+  const IndexComponent = () => {
+    const navigate = useNavigate()
+    return (
+      <React.Fragment>
+        <h1>Index</h1>
+        <button onClick={() => navigate({ to: '/posts' })}>Posts</button>
+        <button
+          onClick={() =>
+            navigate({
+              to: '/posts/$postId/details',
+              params: { postId: 'id1' },
+            })
+          }
+        >
+          To first post
+        </button>
+      </React.Fragment>
+    )
+  }
+
+  const indexRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/',
+    component: IndexComponent,
+  })
+
+  const layoutRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    id: '_layout',
+    component: () => {
+      return (
+        <>
+          <h1>Layout</h1>
+          <Outlet />
+        </>
+      )
+    },
+  })
+
+  const PostsComponent = () => {
+    return (
+      <React.Fragment>
+        <h1>Posts</h1>
+        <Outlet />
+      </React.Fragment>
+    )
+  }
+
+  const postsRoute = createRoute({
+    getParentRoute: () => layoutRoute,
+    path: 'posts',
+    component: PostsComponent,
+  })
+
+  const PostComponent = () => {
+    const params = useParams({ strict: false })
+    return (
+      <React.Fragment>
+        <span>Params: {params.postId}</span>
+        <Outlet />
+      </React.Fragment>
+    )
+  }
+
+  const postRoute = createRoute({
+    getParentRoute: () => postsRoute,
+    path: '$postId',
+    component: PostComponent,
+  })
+
+  const DetailsComponent = () => {
+    const navigate = useNavigate()
+    return (
+      <>
+        <h1>Details!</h1>
+        <button
+          onClick={() => navigate({ from: '/posts/$postId', to: '../$postId' })}
+        >
+          To Post
+        </button>
+      </>
+    )
+  }
+
+  const detailsRoute = createRoute({
+    getParentRoute: () => postRoute,
+    path: 'details',
+    component: DetailsComponent,
+  })
+
+  const InformationComponent = () => {
+    return (
+      <React.Fragment>
+        <h1>Information</h1>
+      </React.Fragment>
+    )
+  }
+
+  const informationRoute = createRoute({
+    getParentRoute: () => postRoute,
+    path: 'info',
+    component: InformationComponent,
+  })
+
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([
+      indexRoute,
+      layoutRoute.addChildren([
+        postsRoute.addChildren([
+          postRoute.addChildren([detailsRoute, informationRoute]),
+        ]),
+      ]),
+    ]),
+  })
+
+  render(<RouterProvider router={router} />)
+
+  const postsButton = await screen.findByRole('button', {
+    name: 'To first post',
+  })
+
+  fireEvent.click(postsButton)
+
+  expect(await screen.findByText('Params: id1')).toBeInTheDocument()
+
+  expect(window.location.pathname).toEqual('/posts/id1/details')
+
+  const postButton = await screen.findByRole('button', {
+    name: 'To Post',
+  })
+
+  fireEvent.click(postButton)
+
+  expect(await screen.findByText('Posts')).toBeInTheDocument()
+
+  expect(window.location.pathname).toEqual('/posts/id1')
+})
+
+test('when navigating from /posts/$postId with an index to ../$postId and the current route is /posts/$postId/details', async () => {
+  const rootRoute = createRootRoute()
+
+  const IndexComponent = () => {
+    const navigate = useNavigate()
+    return (
+      <React.Fragment>
+        <h1>Index</h1>
+        <button onClick={() => navigate({ to: '/posts' })}>Posts</button>
+        <button
+          onClick={() =>
+            navigate({
+              to: '/posts/$postId/details',
+              params: { postId: 'id1' },
+            })
+          }
+        >
+          To first post
+        </button>
+      </React.Fragment>
+    )
+  }
+
+  const indexRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/',
+    component: IndexComponent,
+  })
+
+  const layoutRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    id: '_layout',
+    component: () => {
+      return (
+        <>
+          <h1>Layout</h1>
+          <Outlet />
+        </>
+      )
+    },
+  })
+
+  const PostsComponent = () => {
+    return (
+      <React.Fragment>
+        <h1>Posts</h1>
+        <Outlet />
+      </React.Fragment>
+    )
+  }
+
+  const postsRoute = createRoute({
+    getParentRoute: () => layoutRoute,
+    path: 'posts',
+    component: PostsComponent,
+  })
+
+  const PostComponent = () => {
+    const params = useParams({ strict: false })
+    return (
+      <React.Fragment>
+        <span>Params: {params.postId}</span>
+        <Outlet />
+      </React.Fragment>
+    )
+  }
+
+  const postRoute = createRoute({
+    getParentRoute: () => postsRoute,
+    path: '$postId',
+    component: PostComponent,
+  })
+
+  const postIndexRoute = createRoute({
+    getParentRoute: () => postRoute,
+    path: '/',
+    component: () => <h1>Post Index</h1>,
+  })
+
+  const DetailsComponent = () => {
+    const navigate = useNavigate()
+    return (
+      <>
+        <h1>Details!</h1>
+        <button
+          onClick={() => navigate({ from: '/posts/$postId', to: '../$postId' })}
+        >
+          To Post
+        </button>
+      </>
+    )
+  }
+
+  const detailsRoute = createRoute({
+    getParentRoute: () => postRoute,
+    path: 'details',
+    component: DetailsComponent,
+  })
+
+  const InformationComponent = () => {
+    return (
+      <React.Fragment>
+        <h1>Information</h1>
+      </React.Fragment>
+    )
+  }
+
+  const informationRoute = createRoute({
+    getParentRoute: () => postRoute,
+    path: 'info',
+    component: InformationComponent,
+  })
+
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([
+      indexRoute,
+      layoutRoute.addChildren([
+        postsRoute.addChildren([
+          postRoute.addChildren([
+            postIndexRoute,
+            detailsRoute,
+            informationRoute,
+          ]),
+        ]),
+      ]),
+    ]),
+  })
+
+  render(<RouterProvider router={router} />)
+
+  const postsButton = await screen.findByRole('button', {
+    name: 'To first post',
+  })
+
+  fireEvent.click(postsButton)
+
+  expect(await screen.findByText('Params: id1')).toBeInTheDocument()
+
+  expect(window.location.pathname).toEqual('/posts/id1/details')
+
+  const postButton = await screen.findByRole('button', {
+    name: 'To Post',
+  })
+
+  fireEvent.click(postButton)
+
+  expect(await screen.findByText('Posts')).toBeInTheDocument()
+
+  expect(window.location.pathname).toEqual('/posts/id1')
+})
+
+test('when navigating from /invoices to ./invoiceId and the current route is /posts/$postId/details', async () => {
+  const rootRoute = createRootRoute()
+
+  const IndexComponent = () => {
+    const navigate = useNavigate()
+    return (
+      <React.Fragment>
+        <h1>Index</h1>
+        <button onClick={() => navigate({ to: '/posts' })}>Posts</button>
+        <button
+          onClick={() =>
+            navigate({
+              to: '/posts/$postId/details',
+              params: { postId: 'id1' },
+            })
+          }
+        >
+          To first post
+        </button>
+      </React.Fragment>
+    )
+  }
+
+  const indexRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/',
+    component: IndexComponent,
+  })
+
+  const layoutRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    id: '_layout',
+    component: () => {
+      return (
+        <>
+          <h1>Layout</h1>
+          <Outlet />
+        </>
+      )
+    },
+  })
+
+  const PostsComponent = () => {
+    return (
+      <React.Fragment>
+        <h1>Posts</h1>
+        <Outlet />
+      </React.Fragment>
+    )
+  }
+
+  const postsRoute = createRoute({
+    getParentRoute: () => layoutRoute,
+    path: 'posts',
+    component: PostsComponent,
+  })
+
+  const PostComponent = () => {
+    const params = useParams({ strict: false })
+    return (
+      <React.Fragment>
+        <span>Params: {params.postId}</span>
+        <Outlet />
+      </React.Fragment>
+    )
+  }
+
+  const postRoute = createRoute({
+    getParentRoute: () => postsRoute,
+    path: '$postId',
+    component: PostComponent,
+  })
+
+  const DetailsComponent = () => {
+    const navigate = useNavigate()
+    const [error, setError] = React.useState<unknown>()
+    return (
+      <>
+        <h1>Details!</h1>
+        <button
+          onClick={() => {
+            try {
+              navigate({
+                from: '/invoices',
+                to: './$invoiceId',
+                params: { invoiceId: 'id1' },
+              })
+            } catch (e) {
+              setError(e)
+            }
+          }}
+        >
+          To Invoices
+        </button>
+        <span>Something went wrong!</span>
+      </>
+    )
+  }
+
+  const detailsRoute = createRoute({
+    getParentRoute: () => postRoute,
+    path: 'details',
+    component: DetailsComponent,
+  })
+
+  const InformationComponent = () => {
+    return (
+      <React.Fragment>
+        <h1>Information</h1>
+      </React.Fragment>
+    )
+  }
+
+  const informationRoute = createRoute({
+    getParentRoute: () => postRoute,
+    path: 'info',
+    component: InformationComponent,
+  })
+
+  const invoicesRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: 'invoices',
+    component: () => (
+      <>
+        <h1>Invoices!</h1>
+        <Outlet />
+      </>
+    ),
+  })
+
+  const InvoiceComponent = () => {
+    const params = useParams({ strict: false })
+    return (
+      <>
+        <span>invoiceId: {params.invoiceId}</span>
+      </>
+    )
+  }
+
+  const invoiceRoute = createRoute({
+    getParentRoute: () => invoicesRoute,
+    path: '$invoiceId',
+    component: InvoiceComponent,
+  })
+
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([
+      indexRoute,
+      layoutRoute.addChildren([
+        invoicesRoute.addChildren([invoiceRoute]),
+        postsRoute.addChildren([
+          postRoute.addChildren([detailsRoute, informationRoute]),
+        ]),
+      ]),
+    ]),
+  })
+
+  render(<RouterProvider router={router} />)
+
+  const postsButton = await screen.findByRole('button', {
+    name: 'To first post',
+  })
+
+  fireEvent.click(postsButton)
+
+  const invoicesButton = await screen.findByRole('button', {
+    name: 'To Invoices',
+  })
+
+  fireEvent.click(invoicesButton)
+
+  expect(await screen.findByText('Something went wrong!')).toBeInTheDocument()
+})
+
+test('when navigating to /posts/$postId/info which is masked as /posts/$postId', async () => {
+  const rootRoute = createRootRoute()
+
+  const IndexComponent = () => {
+    const navigate = useNavigate()
+    return (
+      <React.Fragment>
+        <h1>Index</h1>
+        <button
+          onClick={() =>
+            navigate({ to: '/posts/$postId/info', params: { postId: 'id1' } })
+          }
+        >
+          To first post
+        </button>
+      </React.Fragment>
+    )
+  }
+
+  const indexRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/',
+    component: IndexComponent,
+  })
+
+  const PostsComponent = () => {
+    return (
+      <React.Fragment>
+        <h1>Posts</h1>
+        <Outlet />
+      </React.Fragment>
+    )
+  }
+
+  const postsRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: 'posts',
+    component: PostsComponent,
+  })
+
+  const PostComponent = () => {
+    const params = useParams({ strict: false })
+    return (
+      <React.Fragment>
+        <span>Params: {params.postId}</span>
+        <Outlet />
+      </React.Fragment>
+    )
+  }
+
+  const postRoute = createRoute({
+    getParentRoute: () => postsRoute,
+    path: '$postId',
+    component: PostComponent,
+  })
+
+  const InformationComponent = () => {
+    return (
+      <React.Fragment>
+        <h1>Information</h1>
+      </React.Fragment>
+    )
+  }
+
+  const informationRoute = createRoute({
+    getParentRoute: () => postRoute,
+    path: 'info',
+    component: InformationComponent,
+  })
+
+  const routeTree = rootRoute.addChildren([
+    indexRoute,
+    postsRoute.addChildren([postRoute.addChildren([informationRoute])]),
+  ])
+
+  const routeMask = createRouteMask({
+    routeTree,
+    from: '/posts/$postId/info',
+    to: '/posts',
+  })
+
+  const router = createRouter({
+    routeTree,
+    routeMasks: [routeMask],
+  })
+
+  render(<RouterProvider router={router} />)
+
+  const postButton = await screen.findByRole('button', {
+    name: 'To first post',
+  })
+
+  fireEvent.click(postButton)
+
+  expect(await screen.findByText('Params: id1'))
+})
+
+test('when navigating to /posts/$postId/info which is imperatively masked as /posts/$postId', async () => {
+  const rootRoute = createRootRoute()
+
+  const IndexComponent = () => {
+    const navigate = useNavigate()
+    return (
+      <React.Fragment>
+        <h1>Index</h1>
+        <button
+          onClick={() =>
+            navigate({
+              to: '/posts/$postId/info',
+              params: { postId: 'id1' },
+              mask: { to: '/posts/$postId', params: { postId: 'id1' } },
+            })
+          }
+        >
+          To first post
+        </button>
+      </React.Fragment>
+    )
+  }
+
+  const indexRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/',
+    component: IndexComponent,
+  })
+
+  const PostsComponent = () => {
+    return (
+      <React.Fragment>
+        <h1>Posts</h1>
+        <Outlet />
+      </React.Fragment>
+    )
+  }
+
+  const postsRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: 'posts',
+    component: PostsComponent,
+  })
+
+  const PostComponent = () => {
+    const params = useParams({ strict: false })
+    return (
+      <React.Fragment>
+        <span>Params: {params.postId}</span>
+        <Outlet />
+      </React.Fragment>
+    )
+  }
+
+  const postRoute = createRoute({
+    getParentRoute: () => postsRoute,
+    path: '$postId',
+    component: PostComponent,
+  })
+
+  const InformationComponent = () => {
+    return (
+      <React.Fragment>
+        <h1>Information</h1>
+      </React.Fragment>
+    )
+  }
+
+  const informationRoute = createRoute({
+    getParentRoute: () => postRoute,
+    path: 'info',
+    component: InformationComponent,
+  })
+
+  const routeTree = rootRoute.addChildren([
+    indexRoute,
+    postsRoute.addChildren([postRoute.addChildren([informationRoute])]),
+  ])
+
+  const router = createRouter({
+    routeTree,
+  })
+
+  render(<RouterProvider router={router} />)
+
+  const postButton = await screen.findByRole('button', {
+    name: 'To first post',
+  })
+
+  fireEvent.click(postButton)
+
+  expect(await screen.findByText('Information')).toBeInTheDocument()
+
+  expect(window.location.pathname).toEqual('/posts/id1')
+})
diff --git a/packages/react-router/tests/useSearch.test-d.tsx b/packages/react-router/tests/useSearch.test-d.tsx
index 9687bfb5d..73af7e8b5 100644
--- a/packages/react-router/tests/useSearch.test-d.tsx
+++ b/packages/react-router/tests/useSearch.test-d.tsx
@@ -279,3 +279,206 @@ test('when there are overlapping search params', () => {
       | undefined
     >()
 })
+
+test('when the root has no search params but the index route does', () => {
+  const rootRoute = createRootRoute()
+
+  const indexRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/',
+    validateSearch: () => ({ indexPage: 0 }),
+  })
+
+  const invoicesRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: 'invoices',
+  })
+
+  const invoicesIndexRoute = createRoute({
+    getParentRoute: () => invoicesRoute,
+    path: '/',
+  })
+
+  const invoiceRoute = createRoute({
+    getParentRoute: () => invoicesRoute,
+    path: '$invoiceId',
+  })
+
+  const routeTree = rootRoute.addChildren([
+    invoicesRoute.addChildren([invoicesIndexRoute, invoiceRoute]),
+    indexRoute,
+  ])
+
+  const defaultRouter = createRouter({
+    routeTree,
+  })
+
+  type DefaultRouter = typeof defaultRouter
+
+  expectTypeOf(
+    useSearch<DefaultRouter['routeTree'], '/'>,
+  ).returns.toEqualTypeOf<{ indexPage: number }>()
+
+  expectTypeOf(
+    useSearch<DefaultRouter['routeTree'], '__root__'>,
+  ).returns.toEqualTypeOf<{}>()
+
+  expectTypeOf(
+    useSearch<DefaultRouter['routeTree'], '/invoices'>,
+  ).returns.toEqualTypeOf<{}>()
+
+  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/invoices'>)
+    .parameter(0)
+    .toHaveProperty('select')
+    .toEqualTypeOf<((search: {}) => {}) | undefined>()
+
+  expectTypeOf(
+    useSearch<DefaultRouter['routeTree'], '/invoices', false>,
+  ).returns.toEqualTypeOf<{ indexPage?: number }>()
+
+  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/invoices', false>)
+    .parameter(0)
+    .toHaveProperty('select')
+    .toEqualTypeOf<
+      ((search: { indexPage?: number }) => { indexPage?: number }) | undefined
+    >()
+})
+
+test('when the root has search params but the index route does not', () => {
+  const rootRoute = createRootRoute({
+    validateSearch: () => ({ rootPage: 0 }),
+  })
+
+  const indexRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/',
+  })
+
+  const invoicesRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: 'invoices',
+  })
+
+  const invoicesIndexRoute = createRoute({
+    getParentRoute: () => invoicesRoute,
+    path: '/',
+  })
+
+  const invoiceRoute = createRoute({
+    getParentRoute: () => invoicesRoute,
+    path: '$invoiceId',
+  })
+
+  const routeTree = rootRoute.addChildren([
+    invoicesRoute.addChildren([invoicesIndexRoute, invoiceRoute]),
+    indexRoute,
+  ])
+
+  const defaultRouter = createRouter({
+    routeTree,
+  })
+
+  type DefaultRouter = typeof defaultRouter
+
+  expectTypeOf(
+    useSearch<DefaultRouter['routeTree'], '/'>,
+  ).returns.toEqualTypeOf<{ rootPage: number }>()
+
+  expectTypeOf(
+    useSearch<DefaultRouter['routeTree'], '__root__'>,
+  ).returns.toEqualTypeOf<{ rootPage: number }>()
+
+  expectTypeOf(
+    useSearch<DefaultRouter['routeTree'], '/invoices'>,
+  ).returns.toEqualTypeOf<{ rootPage: number }>()
+
+  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/invoices'>)
+    .parameter(0)
+    .toHaveProperty('select')
+    .toEqualTypeOf<
+      ((search: { rootPage: number }) => { rootPage: number }) | undefined
+    >()
+
+  expectTypeOf(
+    useSearch<DefaultRouter['routeTree'], '/invoices', false>,
+  ).returns.toEqualTypeOf<{ rootPage?: number }>()
+
+  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/invoices', false>)
+    .parameter(0)
+    .toHaveProperty('select')
+    .toEqualTypeOf<
+      ((search: { rootPage?: number }) => { rootPage?: number }) | undefined
+    >()
+})
+
+test('when the root has search params but the index does', () => {
+  const rootRoute = createRootRoute({
+    validateSearch: () => ({ rootPage: 0 }),
+  })
+
+  const indexRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/',
+    validateSearch: () => ({ indexPage: 0 }),
+  })
+
+  const invoicesRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: 'invoices',
+  })
+
+  const invoicesIndexRoute = createRoute({
+    getParentRoute: () => invoicesRoute,
+    path: '/',
+  })
+
+  const invoiceRoute = createRoute({
+    getParentRoute: () => invoicesRoute,
+    path: '$invoiceId',
+  })
+
+  const routeTree = rootRoute.addChildren([
+    invoicesRoute.addChildren([invoicesIndexRoute, invoiceRoute]),
+    indexRoute,
+  ])
+
+  const defaultRouter = createRouter({
+    routeTree,
+  })
+
+  type DefaultRouter = typeof defaultRouter
+
+  expectTypeOf(
+    useSearch<DefaultRouter['routeTree'], '/'>,
+  ).returns.toEqualTypeOf<{ rootPage: number; indexPage: number }>()
+
+  expectTypeOf(
+    useSearch<DefaultRouter['routeTree'], '__root__'>,
+  ).returns.toEqualTypeOf<{ rootPage: number }>()
+
+  expectTypeOf(
+    useSearch<DefaultRouter['routeTree'], '/invoices'>,
+  ).returns.toEqualTypeOf<{ rootPage: number }>()
+
+  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/invoices'>)
+    .parameter(0)
+    .toHaveProperty('select')
+    .toEqualTypeOf<
+      ((search: { rootPage: number }) => { rootPage: number }) | undefined
+    >()
+
+  expectTypeOf(
+    useSearch<DefaultRouter['routeTree'], '/invoices', false>,
+  ).returns.toEqualTypeOf<{ indexPage?: number; rootPage?: number }>()
+
+  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/invoices', false>)
+    .parameter(0)
+    .toHaveProperty('select')
+    .toEqualTypeOf<
+      | ((search: { indexPage?: number; rootPage?: number }) => {
+          indexPage?: number
+          rootPage?: number
+        })
+      | undefined
+    >()
+})
diff --git a/packages/router-generator/tests/generator/route-groups/routeTree.snapshot.ts b/packages/router-generator/tests/generator/route-groups/routeTree.snapshot.ts
new file mode 100644
index 000000000..b859aa928
--- /dev/null
+++ b/packages/router-generator/tests/generator/route-groups/routeTree.snapshot.ts
@@ -0,0 +1,57 @@
+/* prettier-ignore-start */
+
+/* eslint-disable */
+
+// @ts-nocheck
+
+// noinspection JSUnusedGlobalSymbols
+
+// This file is auto-generated by TanStack Router
+
+// Import Routes
+
+import { Route as rootRoute } from './routes/__root'
+import { Route as fooAsdfbarIdImport } from './routes/(foo)/asdf/(bar)/$id'
+
+// Create/Update Routes
+
+const fooAsdfbarIdRoute = fooAsdfbarIdImport.update({
+  path: '/asdf/$id',
+  getParentRoute: () => rootRoute,
+} as any)
+
+// Populate the FileRoutesByPath interface
+
+declare module '@tanstack/react-router' {
+  interface FileRoutesByPath {
+    '/(foo)/asdf/(bar)/$id': {
+      id: '/asdf/$id'
+      path: '/asdf/$id'
+      fullPath: '/asdf/$id'
+      preLoaderRoute: typeof fooAsdfbarIdImport
+      parentRoute: typeof rootRoute
+    }
+  }
+}
+
+// Create and export the route tree
+
+export const routeTree = rootRoute.addChildren({ fooAsdfbarIdRoute })
+
+/* prettier-ignore-end */
+
+/* ROUTE_MANIFEST_START
+{
+  "routes": {
+    "__root__": {
+      "filePath": "__root.tsx",
+      "children": [
+        "/asdf/$id"
+      ]
+    },
+    "/asdf/$id": {
+      "filePath": "(foo)/asdf/(bar)/$id.tsx"
+    }
+  }
+}
+ROUTE_MANIFEST_END */
diff --git a/packages/router-generator/tests/generator/route-groups/routes/(foo)/asdf/(bar)/$id.tsx b/packages/router-generator/tests/generator/route-groups/routes/(foo)/asdf/(bar)/$id.tsx
new file mode 100644
index 000000000..75cbf8c35
--- /dev/null
+++ b/packages/router-generator/tests/generator/route-groups/routes/(foo)/asdf/(bar)/$id.tsx
@@ -0,0 +1,5 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/(foo)/asdf/(bar)/$id')({
+  component: () => <div>Hello /(foo)/asdf/(bar)/$id!</div>,
+})
diff --git a/packages/router-generator/tests/generator/route-groups/routes/__root.tsx b/packages/router-generator/tests/generator/route-groups/routes/__root.tsx
new file mode 100644
index 000000000..ab504e42e
--- /dev/null
+++ b/packages/router-generator/tests/generator/route-groups/routes/__root.tsx
@@ -0,0 +1,1 @@
+/** */