Repository: TanStack/router
Pull request #2647 (2024-10-26 20:55:46)

feat(react-router): add support for structural sharing for finegrained selectors

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

CI is running for commit c84fa457d3f0f1d8c80c2a2f82166615e689ada2.

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


---

Sent with 💌 from [NxCloud.](https://nx.app?utm_source=pull-request&utm_medium=comment)
pkg-pr-new[bot]: 
[Open in Stackblitz](https://pkg.pr.new/template/80e8e6d6-c622-4227-a650-fb7a493ca2ae)
<details><summary><b>More templates</b></summary><p>

- [tanstack-router-react-example-basic](https://pkg.pr.new/template/3d988b4c-787c-4f2e-9ec7-ae0546db673d)
- [tanstack-router-react-example-authenticated-routes](https://pkg.pr.new/template/4c0b6480-6bf8-499e-8bc1-8a90d0ade3b3)
- [tanstack-router-react-example-basic-default-search-params](https://pkg.pr.new/template/3db9d75c-f202-4693-98ea-172ba7d9e1e5)
- [tanstack-router-react-example-basic-file-based](https://pkg.pr.new/template/3d565461-ead7-4629-bc0b-58cbbcbf8aa5)
- [tanstack-router-react-example-basic-file-based-codesplitting](https://pkg.pr.new/template/70e2fcbf-0bfd-4720-8c00-5360068c2503)
- [tanstack-router-react-example-react-query](https://pkg.pr.new/template/bb04a09f-edf9-4121-919a-5709111e334f)
- [tanstack-router-react-example-basic-react-query-file-based](https://pkg.pr.new/template/4d6c7c07-da6b-4dc4-99c3-f2c6f1bd7de1)
- [tanstack-router-react-example-basic-ssr-file-based](https://pkg.pr.new/template/122e7fc9-9f42-4be1-83e6-2df366779bb6)
- [tanstack-router-react-example-basic-ssr-streaming-file-based](https://pkg.pr.new/template/adfc37a0-6c13-4634-8e3e-b45bf2b23eb3)
- [tanstack-router-react-example-basic-virtual-file-based](https://pkg.pr.new/template/c659979a-623d-413e-bd0e-2356db43079c)
- [tanstack-router-react-example-basic-virtual-inside-file-based](https://pkg.pr.new/template/d285f0ba-717c-4a95-b66d-e183d0172ac7)
- [tanstack-router-react-example-deferred-data](https://pkg.pr.new/template/1dd5f7fa-5f57-4123-b9e4-d842f0639e7c)
- [tanstack-router-react-example-kitchen-sink](https://pkg.pr.new/template/af0ba859-34fa-4c4a-a853-81f589642dbe)
- [tanstack-router-react-example-kitchen-sink-file-based](https://pkg.pr.new/template/eeab3846-95c5-4961-b257-ecfcbeac19b8)
- [tanstack-router-react-example-kitchen-sink-react-query](https://pkg.pr.new/template/5125b229-42c0-4aac-a4ff-b13c06bd01ce)
- [tanstack-router-react-example-kitchen-sink-react-query-file-based](https://pkg.pr.new/template/6163ae1d-fc9d-4710-a5e1-53ad3b20cdd5)
- [tanstack-router-react-example-large-file-based](https://pkg.pr.new/template/e8c82433-ade9-4664-927a-ac320e37758f)
- [tanstack-router-react-example-location-masking](https://pkg.pr.new/template/27b2556a-7c70-4c92-8a82-3bdc34efe2e2)
- [tanstack-router-react-example-navigation-blocking](https://pkg.pr.new/template/e41bf0b5-ef6e-41da-8bbb-5813b18843aa)
- [tanstack-router-react-example-quickstart](https://pkg.pr.new/template/da0db4ff-73d8-495d-bca7-82f2da2e0d9e)
- [tanstack-router-react-example-quickstart-esbuild-file-based](https://pkg.pr.new/template/b1f3f1bb-e81f-4a19-8c13-88b84e6c6bec)
- [tanstack-router-react-example-quickstart-file-based](https://pkg.pr.new/template/78f797fe-eb52-4486-9cad-364343d45d62)
- [tanstack-router-react-example-quickstart-rspack-file-based](https://pkg.pr.new/template/ae1ad11d-4a69-4470-88f5-633f009d23d8)
- [tanstack-router-react-example-quickstart-webpack-file-based](https://pkg.pr.new/template/a0cd6573-568c-401d-901a-2ad7d06ac96f)
- [router-monorepo-react-query](https://pkg.pr.new/template/35270c19-b1d5-45ee-9652-baee05edd4f3)
- [router-mono-simple](https://pkg.pr.new/template/b41e26e5-dac3-4add-a305-c7613a2773ce)
- [tanstack-router-react-example-scroll-restoration](https://pkg.pr.new/template/6079314c-e778-4da4-8b9c-90a419efe419)
- [tanstack-router-search-validator-adapters](https://pkg.pr.new/template/6e6677e8-66e4-4424-bf87-b9fe37e720e4)
- [tanstack-start-example-basic](https://pkg.pr.new/template/dae23cd3-b27a-495f-84ec-4637e1a8b0ce)
- [tanstack-start-example-basic-auth](https://pkg.pr.new/template/364e71ee-d321-4b09-a01e-8baefa15b4a1)
- [tanstack-start-example-basic-react-query](https://pkg.pr.new/template/6ba5ba31-57b6-4383-a6da-dc48e209ddab)
- [tanstack-start-example-basic-rsc](https://pkg.pr.new/template/51923825-7334-4a17-8f5c-e2bba5f5a53e)
- [tanstack-start-example-clerk-basic](https://pkg.pr.new/template/5428d126-b62a-4f05-ab67-7f036427cb40)
- [tanstack-start-example-convex-trellaux](https://pkg.pr.new/template/be9fd728-dda7-4a45-898a-5022995266dc)
- [tanstack-start-example-counter](https://pkg.pr.new/template/e549374d-442c-4bb4-8d63-f9ae27657829)
- [tanstack-start-example-supabase-basic](https://pkg.pr.new/template/05ff8180-22fa-4ed7-a325-eb2a1537a734)
- [tanstack-start-example-trellaux](https://pkg.pr.new/template/bb513842-dc35-4622-91a0-a23643083d52)
- [tanstack-router-react-example-with-framer-motion](https://pkg.pr.new/template/1e827f38-13fc-4854-8ba7-691b82b78480)
- [tanstack-router-react-example-with-trpc](https://pkg.pr.new/template/0d54712f-27ee-418a-8e35-4e642c5248ef)
- [tanstack-router-react-example-with-trpc-react-query](https://pkg.pr.new/template/8f81d410-14e7-42ef-b94c-7ef12d0a1df1)

</p></details>
      
    


<details><summary><b>@tanstack/create-router</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/create-router@2647
```

</p></details>
      
    

<details><summary><b>@tanstack/eslint-plugin-router</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/eslint-plugin-router@2647
```

</p></details>
      
    

<details><summary><b>@tanstack/react-cross-context</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/react-cross-context@2647
```

</p></details>
      
    

<details><summary><b>@tanstack/react-router</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/react-router@2647
```

</p></details>
      
    

<details><summary><b>@tanstack/history</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/history@2647
```

</p></details>
      
    

<details><summary><b>@tanstack/react-router-with-query</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/react-router-with-query@2647
```

</p></details>
      
    

<details><summary><b>@tanstack/router-arktype-adapter</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/router-arktype-adapter@2647
```

</p></details>
      
    

<details><summary><b>@tanstack/router-cli</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/router-cli@2647
```

</p></details>
      
    

<details><summary><b>@tanstack/router-devtools</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/router-devtools@2647
```

</p></details>
      
    

<details><summary><b>@tanstack/router-generator</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/router-generator@2647
```

</p></details>
      
    

<details><summary><b>@tanstack/router-plugin</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/router-plugin@2647
```

</p></details>
      
    

<details><summary><b>@tanstack/router-valibot-adapter</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/router-valibot-adapter@2647
```

</p></details>
      
    

<details><summary><b>@tanstack/router-vite-plugin</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/router-vite-plugin@2647
```

</p></details>
      
    

<details><summary><b>@tanstack/router-zod-adapter</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/router-zod-adapter@2647
```

</p></details>
      
    

<details><summary><b>@tanstack/start</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/start@2647
```

</p></details>
      
    

<details><summary><b>@tanstack/start-vite-plugin</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/start-vite-plugin@2647
```

</p></details>
      
    

<details><summary><b>@tanstack/virtual-file-routes</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/virtual-file-routes@2647
```

</p></details>
      
    

_commit: <a href="https://github.com/TanStack/router/runs/32109428078"><code>c84fa45</code></a>_


Patch:
diff --git a/docs/config.json b/docs/config.json
index 8d015329e..438209691 100644
--- a/docs/config.json
+++ b/docs/config.json
@@ -175,6 +175,10 @@
             {
               "label": "SSR",
               "to": "framework/react/guide/ssr"
+            },
+            {
+              "label": "Render Optimizations",
+              "to": "framework/react/guide/render-optimizations"
             }
           ]
         }
diff --git a/docs/framework/react/api/router/RouteApiType.md b/docs/framework/react/api/router/RouteApiType.md
index 1204cbd05..a5d806397 100644
--- a/docs/framework/react/api/router/RouteApiType.md
+++ b/docs/framework/react/api/router/RouteApiType.md
@@ -23,6 +23,11 @@ The `RouteApi` has the following properties and methods:
     - Optional
     - `(match: RouteMatch) => TSelected`
     - If supplied, this function will be called with the route match and the return value will be returned from `useMatch`. This value will also be used to determine if the hook should re-render its parent component using shallow equality checks.
+  - `opts.structuralSharing`
+    - Optional
+    - `boolean`
+    - Configures whether structural sharing is enabled for the value returned by `select`.
+    - See the [Render Optimizations guide](../../guide/render-optimizations.md) for more information.
 - Returns
   - If a `select` function is provided, the return value of the `select` function.
   - If no `select` function is provided, the `RouteMatch` object or a loosened version of the `RouteMatch` object if `opts.strict` is `false`.
@@ -59,6 +64,11 @@ The `RouteApi` has the following properties and methods:
     - Optional
     - `(match: TFullSearchSchema) => TSelected`
     - If supplied, this function will be called with the route match and the return value will be returned from `useSearch`. This value will also be used to determine if the hook should re-render its parent component using shallow equality checks.
+  - `opts.structuralSharing`
+    - Optional
+    - `boolean`
+    - Configures whether structural sharing is enabled for the value returned by `select`.
+    - See the [Render Optimizations guide](../../guide/render-optimizations.md) for more information.
 - Returns
   - If a `select` function is provided, the return value of the `select` function.
   - If no `select` function is provided, the `TFullSearchSchema` object or a loosened version of the `TFullSearchSchema` object if `opts.strict` is `false`.
@@ -77,6 +87,11 @@ The `RouteApi` has the following properties and methods:
     - Optional
     - `(match: TAllParams) => TSelected`
     - If supplied, this function will be called with the route match and the return value will be returned from `useParams`. This value will also be used to determine if the hook should re-render its parent component using shallow equality checks.
+  - `opts.structuralSharing`
+    - Optional
+    - `boolean`
+    - Configures whether structural sharing is enabled for the value returned by `select`.
+    - See the [Render Optimizations guide](../../guide/render-optimizations.md) for more information.
 - Returns
   - If a `select` function is provided, the return value of the `select` function.
   - If no `select` function is provided, the `TAllParams` object or a loosened version of the `TAllParams` object if `opts.strict` is `false`.
@@ -95,6 +110,11 @@ The `RouteApi` has the following properties and methods:
     - Optional
     - `(match: TLoaderData) => TSelected`
     - If supplied, this function will be called with the route match and the return value will be returned from `useLoaderData`. This value will also be used to determine if the hook should re-render its parent component using shallow equality checks.
+  - `opts.structuralSharing`
+    - Optional
+    - `boolean`
+    - Configures whether structural sharing is enabled for the value returned by `select`.
+    - See the [Render Optimizations guide](../../guide/render-optimizations.md) for more information.
 - Returns
   - If a `select` function is provided, the return value of the `select` function.
   - If no `select` function is provided, the `TLoaderData` object or a loosened version of the `TLoaderData` object if `opts.strict` is `false`.
@@ -113,6 +133,11 @@ The `RouteApi` has the following properties and methods:
     - Optional
     - `(match: TLoaderDeps) => TSelected`
     - If supplied, this function will be called with the route match and the return value will be returned from `useLoaderDeps`.
+  - `opts.structuralSharing`
+    - Optional
+    - `boolean`
+    - Configures whether structural sharing is enabled for the value returned by `select`.
+    - See the [Render Optimizations guide](../../guide/render-optimizations.md) for more information.
 - Returns
   - If a `select` function is provided, the return value of the `select` function.
   - If no `select` function is provided, the `TLoaderDeps` object.
diff --git a/docs/framework/react/api/router/RouterOptionsType.md b/docs/framework/react/api/router/RouterOptionsType.md
index d2fee5f02..6c98ca28d 100644
--- a/docs/framework/react/api/router/RouterOptionsType.md
+++ b/docs/framework/react/api/router/RouterOptionsType.md
@@ -292,3 +292,11 @@ const router = createRouter({
 - Type: `Array<';' | ':' | '@' | '&' | '=' | '+' | '$' | ','>`
 - Optional
 - Configures which URI characters are allowed in path params that would ordinarily be escaped by encodeURIComponent.
+
+### `defaultStructuralSharing` property
+
+- Type: `boolean`
+- Optional
+- Defaults to `false`
+- Configures whether structural sharing is enabled by default for fine-grained selectors.
+- See the [Render Optimizations guide](../../guide/render-optimizations.md) for more information.
diff --git a/docs/framework/react/api/router/useChildMatchesHook.md b/docs/framework/react/api/router/useChildMatchesHook.md
index 0d6e0f942..cf31d2101 100644
--- a/docs/framework/react/api/router/useChildMatchesHook.md
+++ b/docs/framework/react/api/router/useChildMatchesHook.md
@@ -18,6 +18,13 @@ The `useChildMatches` hook accepts a single _optional_ argument, an `options` ob
 - `(matches: RouteMatch[]) => TSelected`
 - If supplied, this function will be called with the route matches and the return value will be returned from `useChildMatches`. This value will also be used to determine if the hook should re-render its parent component using shallow equality checks.
 
+### `opts.structuralSharing` option
+
+- Type: `boolean`
+- Optional
+- Configures whether structural sharing is enabled for the value returned by `select`.
+- See the [Render Optimizations guide](../../guide/render-optimizations.md) for more information.
+
 ## useChildMatches returns
 
 - If a `select` function is provided, the return value of the `select` function.
diff --git a/docs/framework/react/api/router/useLoaderDataHook.md b/docs/framework/react/api/router/useLoaderDataHook.md
index d6de4b8ea..dfcf2d375 100644
--- a/docs/framework/react/api/router/useLoaderDataHook.md
+++ b/docs/framework/react/api/router/useLoaderDataHook.md
@@ -29,6 +29,13 @@ The `useLoaderData` hook accepts an `options` object.
 - `(loaderData: TLoaderData) => TSelected`
 - If supplied, this function will be called with the loader data and the return value will be returned from `useLoaderData`. This value will also be used to determine if the hook should re-render its parent component using shallow equality checks.
 
+### `opts.structuralSharing` option
+
+- Type: `boolean`
+- Optional
+- Configures whether structural sharing is enabled for the value returned by `select`.
+- See the [Render Optimizations guide](../../guide/render-optimizations.md) for more information.
+
 ## useLoaderData returns
 
 - If a `select` function is provided, the return value of the `select` function.
diff --git a/docs/framework/react/api/router/useLoaderDepsHook.md b/docs/framework/react/api/router/useLoaderDepsHook.md
index bcd653665..bf9a3a35e 100644
--- a/docs/framework/react/api/router/useLoaderDepsHook.md
+++ b/docs/framework/react/api/router/useLoaderDepsHook.md
@@ -21,6 +21,13 @@ The `useLoaderDepsHook` hook accepts an `options` object.
 - Optional
 - If supplied, this function will be called with the loader dependencies object and the return value will be returned from `useLoaderDeps`.
 
+### `opts.structuralSharing` option
+
+- Type: `boolean`
+- Optional
+- Configures whether structural sharing is enabled for the value returned by `select`.
+- See the [Render Optimizations guide](../../guide/render-optimizations.md) for more information.
+
 ## useLoaderDeps returns
 
 - An object of the loader dependencies or `TSelected` if a `select` function is provided.
diff --git a/docs/framework/react/api/router/useMatchHook.md b/docs/framework/react/api/router/useMatchHook.md
index 1ce050486..966fd1d7c 100644
--- a/docs/framework/react/api/router/useMatchHook.md
+++ b/docs/framework/react/api/router/useMatchHook.md
@@ -30,6 +30,13 @@ The `useMatch` hook accepts a single argument, an `options` object.
 - `(match: RouteMatch) => TSelected`
 - If supplied, this function will be called with the route match and the return value will be returned from `useMatch`. This value will also be used to determine if the hook should re-render its parent component using shallow equality checks.
 
+### `opts.structuralSharing` option
+
+- Type: `boolean`
+- Optional
+- Configures whether structural sharing is enabled for the value returned by `select`.
+- See the [Render Optimizations guide](../../guide/render-optimizations.md) for more information.
+
 ### `opts.shouldThrow` option
 
 - Type: `boolean`
diff --git a/docs/framework/react/api/router/useMatchesHook.md b/docs/framework/react/api/router/useMatchesHook.md
index 74a554dfd..d7a1be8dc 100644
--- a/docs/framework/react/api/router/useMatchesHook.md
+++ b/docs/framework/react/api/router/useMatchesHook.md
@@ -18,6 +18,13 @@ The `useMatches` hook accepts a single _optional_ argument, an `options` object.
 - `(matches: RouteMatch[]) => TSelected`
 - If supplied, this function will be called with the route matches and the return value will be returned from `useMatches`. This value will also be used to determine if the hook should re-render its parent component using shallow equality checks.
 
+### `opts.structuralSharing` option
+
+- Type: `boolean`
+- Optional
+- Configures whether structural sharing is enabled for the value returned by `select`.
+- See the [Render Optimizations guide](../../guide/render-optimizations.md) for more information.
+
 ## useMatches returns
 
 - If a `select` function is provided, the return value of the `select` function.
diff --git a/docs/framework/react/api/router/useParamsHook.md b/docs/framework/react/api/router/useParamsHook.md
index 41ad2da50..d5068325c 100644
--- a/docs/framework/react/api/router/useParamsHook.md
+++ b/docs/framework/react/api/router/useParamsHook.md
@@ -21,6 +21,13 @@ The `useParams` hook accepts an optional `options` object.
 - `(params: AllParams) => TSelected`
 - If supplied, this function will be called with the params object and the return value will be returned from `useParams`. This value will also be used to determine if the hook should re-render its parent component using shallow equality checks.
 
+### `opts.structuralSharing` option
+
+- Type: `boolean`
+- Optional
+- Configures whether structural sharing is enabled for the value returned by `select`.
+- See the [Render Optimizations guide](../../guide/render-optimizations.md) for more information.
+
 ## useParams returns
 
 - An object of of the match's and parent match path params or `TSelected` if a `select` function is provided.
diff --git a/docs/framework/react/api/router/useParentMatchesHook.md b/docs/framework/react/api/router/useParentMatchesHook.md
index bc50c3cd5..fe4068f39 100644
--- a/docs/framework/react/api/router/useParentMatchesHook.md
+++ b/docs/framework/react/api/router/useParentMatchesHook.md
@@ -18,6 +18,13 @@ The `useParentMatches` hook accepts an optional `options` object.
 - `(matches: RouteMatch[]) => TSelected`
 - If supplied, this function will be called with the route matches and the return value will be returned from `useParentMatches`. This value will also be used to determine if the hook should re-render its parent component using shallow equality checks.
 
+### `opts.structuralSharing` option
+
+- Type: `boolean`
+- Optional
+- Configures whether structural sharing is enabled for the value returned by `select`.
+- See the [Render Optimizations guide](../../guide/render-optimizations.md) for more information.
+
 ## useParentMatches returns
 
 - If a `select` function is provided, the return value of the `select` function.
diff --git a/docs/framework/react/api/router/useRouterStateHook.md b/docs/framework/react/api/router/useRouterStateHook.md
index dbcb37613..bcded1d41 100644
--- a/docs/framework/react/api/router/useRouterStateHook.md
+++ b/docs/framework/react/api/router/useRouterStateHook.md
@@ -18,6 +18,13 @@ The `useRouterState` hook accepts an optional `options` object.
 - Optional
 - If supplied, this function will be called with the [`RouterState`](./RouterStateType.md) object and the return value will be returned from `useRouterState`.
 
+### `opts.structuralSharing` option
+
+- Type: `boolean`
+- Optional
+- Configures whether structural sharing is enabled for the value returned by `select`.
+- See the [Render Optimizations guide](../../guide/render-optimizations.md) for more information.
+
 ## useRouterState returns
 
 - The current [`RouterState`](./RouterStateType.md) object or `TSelected` if a `select` function is provided.
diff --git a/docs/framework/react/api/router/useSearchHook.md b/docs/framework/react/api/router/useSearchHook.md
index 586ce2e4d..e911d8d13 100644
--- a/docs/framework/react/api/router/useSearchHook.md
+++ b/docs/framework/react/api/router/useSearchHook.md
@@ -21,6 +21,13 @@ The `useSearch` hook accepts an `options` object.
 - Optional
 - If supplied, this function will be called with the search object and the return value will be returned from `useSearch`.
 
+### `opts.structuralSharing` option
+
+- Type: `boolean`
+- Optional
+- Configures whether structural sharing is enabled for the value returned by `select`.
+- See the [Render Optimizations guide](../../guide/render-optimizations.md) for more information.
+
 ### `opts.strict` option
 
 - Type: `boolean`
diff --git a/docs/framework/react/guide/render-optimizations.md b/docs/framework/react/guide/render-optimizations.md
new file mode 100644
index 000000000..a01d6b4de
--- /dev/null
+++ b/docs/framework/react/guide/render-optimizations.md
@@ -0,0 +1,88 @@
+---
+title: Render Optimizations
+---
+
+TanStack Router includes several optimizations to ensure your components only re-render when necessary. These optimizations include:
+
+## structural sharing
+
+TanStack Router uses a technique called "structural sharing" to preserve as many references as possible between re-renders, which is particularly useful for state stored in the URL, such as search parameters.
+
+For example, consider a `details` route with two search parameters, `foo` and `bar`, accessed like this:
+
+```tsx
+const search = Route.useSearch()
+```
+
+When only `bar` is changed by navigating from `/details?foo=f1&bar=b1` to `/details?foo=f1&bar=b2`, `search.foo` will be referentially stable and only `search.bar` will be replaced.
+
+## fine-grained selectors
+
+You can access and subscribe to the router state using various hooks like `useRouterState`, `useSearch`, and others. If you only want a specific component to re-render when a particular subset of the router state such as a subset of the search parameters changes, you can use partial subscriptions with the `select` property.
+
+```tsx
+// component won't re-render when `bar` changes
+const foo = Route.useSearch({ select: ({ foo }) => foo })
+```
+
+### structural sharing with fine-grained selectors
+
+The `select` function can perform various calculations on the router state, allowing you to return different types of values, such as objects. For example:
+
+```tsx
+const result = Route.useSearch({
+  select: (search) => {
+    return {
+      foo: search.foo,
+      hello: `hello ${search.foo}`,
+    }
+  },
+})
+```
+
+Although this works, it will cause your component to re-render each time, since `select` is now returning a new object each time it’s called.
+
+You can avoid this re-rendering issue by using "structural sharing" as described above. By default, structural sharing is turned off to maintain backward compatibility, but this may change in v2.
+
+To enable structural sharing for fine grained selectors, you have two options:
+
+#### Enable it by default in the router options:
+
+```tsx
+const router = createRouter({
+  routeTree,
+  defaultStructuralSharing: true,
+})
+```
+
+#### Enable it per hook usage as shown here:
+
+```tsx
+const result = Route.useSearch({
+  select: (search) => {
+    return {
+      foo: search.foo,
+      hello: `hello ${search.foo}`,
+    }
+  },
+  structuralSharing: true,
+})
+```
+
+> [!IMPORTANT]
+> Structural sharing only works with JSON-compatible data. This means you cannot use `select` to return items like class instances if structural sharing is enabled.
+
+In line with TanStack Router's type-safe design, TypeScript will raise an error if you attempt the following:
+
+```tsx
+const result = Route.useSearch({
+  select: (search) => {
+    return {
+      date: new Date(),
+    }
+  },
+  structuralSharing: true,
+})
+```
+
+If structural sharing is enabled by default in the router options, you can prevent this error by setting `structuralSharing: false`.
diff --git a/packages/react-router/src/Match.tsx b/packages/react-router/src/Match.tsx
index 57c8754c5..b0602e646 100644
--- a/packages/react-router/src/Match.tsx
+++ b/packages/react-router/src/Match.tsx
@@ -120,9 +120,10 @@ export const MatchInner = React.memo(function MatchInnerImpl({
       return {
         routeId,
         matchIndex,
-        match: pick(match, ['id', 'status', 'error', 'loadPromise']),
+        match: pick(match, ['id', 'status', 'error']),
       }
     },
+    structuralSharing: true as any,
   })
 
   const route = router.routesById[routeId]!
@@ -180,7 +181,7 @@ export const MatchInner = React.memo(function MatchInnerImpl({
     //   false,
     //   'Tried to render a redirected route match! This is a weird circumstance, please file an issue!',
     // )
-    throw match.loadPromise
+    throw router.getMatch(match.id)?.loadPromise
   }
 
   if (match.status === 'error') {
@@ -237,7 +238,7 @@ export const MatchInner = React.memo(function MatchInnerImpl({
         }, pendingMinMs)
       }
     }
-    throw match.loadPromise
+    throw router.getMatch(match.id)?.loadPromise
   }
 
   return (
@@ -259,7 +260,7 @@ export const Outlet = React.memo(function OutletImpl() {
 
   const route = router.routesById[routeId]!
 
-  const { parentGlobalNotFound } = useRouterState({
+  const parentGlobalNotFound = useRouterState({
     select: (s) => {
       const matches = s.matches
       const parentMatch = matches.find((d) => d.id === matchId)
@@ -267,9 +268,7 @@ export const Outlet = React.memo(function OutletImpl() {
         parentMatch,
         `Could not find parent match for matchId "${matchId}"`,
       )
-      return {
-        parentGlobalNotFound: parentMatch.globalNotFound,
-      }
+      return parentMatch.globalNotFound
     },
   })
 
diff --git a/packages/react-router/src/Matches.tsx b/packages/react-router/src/Matches.tsx
index a99403938..0bae2cf2b 100644
--- a/packages/react-router/src/Matches.tsx
+++ b/packages/react-router/src/Matches.tsx
@@ -7,8 +7,12 @@ import { Transitioner } from './Transitioner'
 import { matchContext } from './matchContext'
 import { Match } from './Match'
 import { SafeFragment } from './SafeFragment'
+import type {
+  StructuralSharingOption,
+  ValidateSelected,
+} from './structuralSharing'
 import type { AnyRoute, ReactNode, StaticDataRouteOption } from './route'
-import type { AnyRouter, RegisteredRouter } from './router'
+import type { AnyRouter, RegisteredRouter, RouterState } from './router'
 import type { ResolveRelativePath, ToOptions } from './link'
 import type {
   AllContext,
@@ -288,6 +292,7 @@ export function useMatchRoute<TRouter extends AnyRouter = RegisteredRouter>() {
 
   useRouterState({
     select: (s) => [s.location.href, s.resolvedLocation.href, s.status],
+    structuralSharing: true as any,
   })
 
   return React.useCallback(
@@ -369,56 +374,79 @@ export type MakeRouteMatchUnion<
     >
   : never
 
+export interface UseMatchesBaseOptions<
+  TRouter extends AnyRouter,
+  TSelected,
+  TStructuralSharing,
+> {
+  select?: (
+    matches: Array<MakeRouteMatchUnion<TRouter>>,
+  ) => ValidateSelected<TRouter, TSelected, TStructuralSharing>
+}
+
+export type UseMatchesResult<
+  TRouter extends AnyRouter,
+  TSelected,
+> = unknown extends TSelected ? Array<MakeRouteMatchUnion<TRouter>> : TSelected
+
 export function useMatches<
   TRouter extends AnyRouter = RegisteredRouter,
-  TRouteMatch = MakeRouteMatchUnion<TRouter>,
-  T = Array<TRouteMatch>,
->(opts?: { select?: (matches: Array<TRouteMatch>) => T }): T {
+  TSelected = unknown,
+  TStructuralSharing extends boolean = boolean,
+>(
+  opts?: UseMatchesBaseOptions<TRouter, TSelected, TStructuralSharing> &
+    StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
+): UseMatchesResult<TRouter, TSelected> {
   return useRouterState({
-    select: (state) => {
+    select: (state: RouterState<TRouter['routeTree']>) => {
       const matches = state.matches
       return opts?.select
-        ? opts.select(matches as Array<TRouteMatch>)
-        : (matches as T)
+        ? opts.select(matches as Array<MakeRouteMatchUnion<TRouter>>)
+        : matches
     },
-  })
+    structuralSharing: opts?.structuralSharing,
+  } as any) as UseMatchesResult<TRouter, TSelected>
 }
 
 export function useParentMatches<
   TRouter extends AnyRouter = RegisteredRouter,
-  TRouteMatch = MakeRouteMatchUnion<TRouter>,
-  T = Array<TRouteMatch>,
->(opts?: { select?: (matches: Array<TRouteMatch>) => T }): T {
+  TSelected = unknown,
+  TStructuralSharing extends boolean = boolean,
+>(
+  opts?: UseMatchesBaseOptions<TRouter, TSelected, TStructuralSharing> &
+    StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
+): UseMatchesResult<TRouter, TSelected> {
   const contextMatchId = React.useContext(matchContext)
 
   return useMatches({
-    select: (matches) => {
+    select: (matches: Array<MakeRouteMatchUnion<TRouter>>) => {
       matches = matches.slice(
         0,
         matches.findIndex((d) => d.id === contextMatchId),
       )
-      return opts?.select
-        ? opts.select(matches as Array<TRouteMatch>)
-        : (matches as T)
+      return opts?.select ? opts.select(matches) : matches
     },
-  })
+    structuralSharing: opts?.structuralSharing,
+  } as any)
 }
 
 export function useChildMatches<
   TRouter extends AnyRouter = RegisteredRouter,
-  TRouteMatch = MakeRouteMatchUnion<TRouter>,
-  T = Array<TRouteMatch>,
->(opts?: { select?: (matches: Array<TRouteMatch>) => T }): T {
+  TSelected = unknown,
+  TStructuralSharing extends boolean = boolean,
+>(
+  opts?: UseMatchesBaseOptions<TRouter, TSelected, TStructuralSharing> &
+    StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
+): UseMatchesResult<TRouter, TSelected> {
   const contextMatchId = React.useContext(matchContext)
 
   return useMatches({
-    select: (matches) => {
+    select: (matches: Array<MakeRouteMatchUnion<TRouter>>) => {
       matches = matches.slice(
         matches.findIndex((d) => d.id === contextMatchId) + 1,
       )
-      return opts?.select
-        ? opts.select(matches as Array<TRouteMatch>)
-        : (matches as T)
+      return opts?.select ? opts.select(matches) : matches
     },
-  })
+    structuralSharing: opts?.structuralSharing,
+  } as any)
 }
diff --git a/packages/react-router/src/RouterProvider.tsx b/packages/react-router/src/RouterProvider.tsx
index 4a49cd1de..b3f0c59f2 100644
--- a/packages/react-router/src/RouterProvider.tsx
+++ b/packages/react-router/src/RouterProvider.tsx
@@ -105,18 +105,21 @@ export type RouterProps<
   RouterOptions<
     TRouter['routeTree'],
     NonNullable<TRouter['options']['trailingSlash']>,
+    NonNullable<TRouter['options']['defaultStructuralSharing']>,
     TDehydrated
   >,
   'context'
 > & {
   router: Router<
     TRouter['routeTree'],
-    NonNullable<TRouter['options']['trailingSlash']>
+    NonNullable<TRouter['options']['trailingSlash']>,
+    NonNullable<TRouter['options']['defaultStructuralSharing']>
   >
   context?: Partial<
     RouterOptions<
       TRouter['routeTree'],
       NonNullable<TRouter['options']['trailingSlash']>,
+      NonNullable<TRouter['options']['defaultStructuralSharing']>,
       TDehydrated
     >['context']
   >
diff --git a/packages/react-router/src/Transitioner.tsx b/packages/react-router/src/Transitioner.tsx
index 4a64c5d20..31c971321 100644
--- a/packages/react-router/src/Transitioner.tsx
+++ b/packages/react-router/src/Transitioner.tsx
@@ -1,5 +1,5 @@
 import * as React from 'react'
-import { pick, useLayoutEffect, usePrevious } from './utils'
+import { useLayoutEffect, usePrevious } from './utils'
 import { useRouter } from './useRouter'
 import { useRouterState } from './useRouterState'
 import { trimPathRight } from './path'
@@ -7,24 +7,23 @@ import { trimPathRight } from './path'
 export function Transitioner() {
   const router = useRouter()
   const mountLoadForRouter = React.useRef({ router, mounted: false })
-  const routerState = useRouterState({
-    select: (s) =>
-      pick(s, ['isLoading', 'location', 'resolvedLocation', 'isTransitioning']),
+  const isLoading = useRouterState({
+    select: ({ isLoading }) => isLoading,
   })
 
   const [isTransitioning, startReactTransition_] = React.useTransition()
   // Track pending state changes
   const hasPendingMatches = useRouterState({
     select: (s) => s.matches.some((d) => d.status === 'pending'),
+    structuralSharing: true,
   })
 
-  const previousIsLoading = usePrevious(routerState.isLoading)
+  const previousIsLoading = usePrevious(isLoading)
 
-  const isAnyPending =
-    routerState.isLoading || isTransitioning || hasPendingMatches
+  const isAnyPending = isLoading || isTransitioning || hasPendingMatches
   const previousIsAnyPending = usePrevious(isAnyPending)
 
-  const isPagePending = routerState.isLoading || hasPendingMatches
+  const isPagePending = isLoading || hasPendingMatches
   const previousIsPagePending = usePrevious(isPagePending)
 
   if (!router.isServer) {
@@ -81,7 +80,7 @@ export function Transitioner() {
 
   useLayoutEffect(() => {
     // The router was loading and now it's not
-    if (previousIsLoading && !routerState.isLoading) {
+    if (previousIsLoading && !isLoading) {
       const toLocation = router.state.location
       const fromLocation = router.state.resolvedLocation
       const pathChanged = fromLocation.pathname !== toLocation.pathname
@@ -93,7 +92,7 @@ export function Transitioner() {
         pathChanged,
       })
     }
-  }, [previousIsLoading, router, routerState.isLoading])
+  }, [previousIsLoading, router, isLoading])
 
   useLayoutEffect(() => {
     // emit onBeforeRouteMount
diff --git a/packages/react-router/src/fileRoute.ts b/packages/react-router/src/fileRoute.ts
index 7b9a25532..d5c6fbd86 100644
--- a/packages/react-router/src/fileRoute.ts
+++ b/packages/react-router/src/fileRoute.ts
@@ -1,11 +1,15 @@
 import warning from 'tiny-warning'
 import { createRoute } from './route'
+
 import { useMatch } from './useMatch'
 import { useLoaderDeps } from './useLoaderDeps'
 import { useLoaderData } from './useLoaderData'
 import { useSearch } from './useSearch'
 import { useParams } from './useParams'
 import { useNavigate } from './useNavigate'
+import type { UseParamsRoute } from './useParams'
+import type { UseMatchRoute } from './useMatch'
+import type { UseSearchRoute } from './useSearch'
 import type { Constrain } from './utils'
 import type {
   AnyContext,
@@ -20,9 +24,11 @@ import type {
   RouteLoaderFn,
   UpdatableRouteOptions,
 } from './route'
-import type { MakeRouteMatch } from './Matches'
 import type { RegisteredRouter } from './router'
 import type { RouteById, RouteIds } from './routeInfo'
+import type { UseLoaderDepsRoute } from './useLoaderDeps'
+import type { UseLoaderDataRoute } from './useLoaderData'
+import type { UseRouteContextRoute } from './useRouteContext'
 
 export interface FileRoutesByPath {
   // '/': {
@@ -208,48 +214,42 @@ export class LazyRoute<TRoute extends AnyRoute> {
     ;(this as any).$$typeof = Symbol.for('react.memo')
   }
 
-  useMatch = <
-    TRouteMatch = MakeRouteMatch<
-      RegisteredRouter['routeTree'],
-      TRoute['types']['id']
-    >,
-    TSelected = TRouteMatch,
-  >(opts?: {
-    select?: (match: TRouteMatch) => TSelected
-  }): TSelected => {
-    return useMatch({ select: opts?.select, from: this.options.id })
+  useMatch: UseMatchRoute<TRoute['id']> = (opts) => {
+    return useMatch({
+      select: opts?.select,
+      from: this.options.id,
+      structuralSharing: opts?.structuralSharing,
+    } as any) as any
   }
 
-  useRouteContext = <TSelected = TRoute['types']['allContext']>(opts?: {
-    select?: (s: TRoute['types']['allContext']) => TSelected
-  }): TSelected => {
+  useRouteContext: UseRouteContextRoute<TRoute['id']> = (opts) => {
     return useMatch({
       from: this.options.id,
       select: (d: any) => (opts?.select ? opts.select(d.context) : d.context),
-    })
+    }) as any
   }
 
-  useSearch = <TSelected = TRoute['types']['fullSearchSchema']>(opts?: {
-    select?: (s: TRoute['types']['fullSearchSchema']) => TSelected
-  }): TSelected => {
-    return useSearch({ ...opts, from: this.options.id })
+  useSearch: UseSearchRoute<TRoute['id']> = (opts) => {
+    return useSearch({
+      select: opts?.select,
+      structuralSharing: opts?.structuralSharing,
+      from: this.options.id,
+    } as any)
   }
 
-  useParams = <TSelected = TRoute['types']['allParams']>(opts?: {
-    select?: (s: TRoute['types']['allParams']) => TSelected
-  }): TSelected => {
-    return useParams({ ...opts, from: this.options.id })
+  useParams: UseParamsRoute<TRoute['id']> = (opts) => {
+    return useParams({
+      select: opts?.select,
+      structuralSharing: opts?.structuralSharing,
+      from: this.options.id,
+    } as any)
   }
 
-  useLoaderDeps = <TSelected = TRoute['types']['loaderDeps']>(opts?: {
-    select?: (s: TRoute['types']['loaderDeps']) => TSelected
-  }): TSelected => {
+  useLoaderDeps: UseLoaderDepsRoute<TRoute['id']> = (opts) => {
     return useLoaderDeps({ ...opts, from: this.options.id } as any)
   }
 
-  useLoaderData = <TSelected = TRoute['types']['loaderData']>(opts?: {
-    select?: (s: TRoute['types']['loaderData']) => TSelected
-  }): TSelected => {
+  useLoaderData: UseLoaderDataRoute<TRoute['id']> = (opts) => {
     return useLoaderData({ ...opts, from: this.options.id } as any)
   }
 
diff --git a/packages/react-router/src/link.tsx b/packages/react-router/src/link.tsx
index 3a97ae5a5..63c421b6f 100644
--- a/packages/react-router/src/link.tsx
+++ b/packages/react-router/src/link.tsx
@@ -233,10 +233,10 @@ export interface MaskOptions<
 }
 
 export type ToMaskOptions<
-  TRouteTree extends AnyRouter = RegisteredRouter,
+  TRouter extends AnyRouter = RegisteredRouter,
   TMaskFrom extends string = string,
   TMaskTo extends string = '.',
-> = ToSubOptions<TRouteTree, TMaskFrom, TMaskTo> & {
+> = ToSubOptions<TRouter, TMaskFrom, TMaskTo> & {
   unmaskOnReload?: boolean
 }
 
@@ -645,7 +645,10 @@ export function useLinkProps<
   }, [to])
 
   // subscribe to search params to re-build location if it changes
-  const currentSearch = useRouterState({ select: (s) => s.location.search })
+  const currentSearch = useRouterState({
+    select: (s) => s.location.search,
+    structuralSharing: true as any,
+  })
 
   const next = React.useMemo(
     () => router.buildLocation(options as any),
diff --git a/packages/react-router/src/route.ts b/packages/react-router/src/route.ts
index 63e23eb5b..ed36934ba 100644
--- a/packages/react-router/src/route.ts
+++ b/packages/react-router/src/route.ts
@@ -1,18 +1,22 @@
 import invariant from 'tiny-invariant'
-import { useMatch } from './useMatch'
-import { useLoaderDeps } from './useLoaderDeps'
-import { useLoaderData } from './useLoaderData'
 import { joinPaths, trimPathLeft } from './path'
+import { useLoaderData } from './useLoaderData'
+import { useLoaderDeps } from './useLoaderDeps'
 import { useParams } from './useParams'
 import { useSearch } from './useSearch'
 import { notFound } from './not-found'
 import { useNavigate } from './useNavigate'
 import { rootRouteId } from './root'
+import { useMatch } from './useMatch'
+import type { UseLoaderDataRoute } from './useLoaderData'
+import type { UseMatchRoute } from './useMatch'
+import type { UseLoaderDepsRoute } from './useLoaderDeps'
+import type { UseParamsRoute } from './useParams'
+import type { UseSearchRoute } from './useSearch'
 import type * as React from 'react'
 import type { RootRouteId } from './root'
 import type { UseNavigateResult } from './useNavigate'
 import type {
-  MakeRouteMatch,
   MakeRouteMatchFromRoute,
   MakeRouteMatchUnion,
   RouteMatch,
@@ -25,6 +29,7 @@ import type { Assign, Constrain, Expand, NoInfer } from './utils'
 import type { BuildLocationFn, NavigateFn } from './RouterProvider'
 import type { NotFoundError } from './not-found'
 import type { LazyRoute } from './fileRoute'
+import type { UseRouteContextRoute } from './useRouteContext'
 
 export type AnyPathParams = {}
 
@@ -845,60 +850,42 @@ export class RouteApi<TId, TRouter extends AnyRouter = RegisteredRouter> {
     this.id = id as any
   }
 
-  useMatch = <
-    TRouteTree extends AnyRoute = TRouter['routeTree'],
-    TRouteMatch = MakeRouteMatch<TRouteTree, TId>,
-    TSelected = TRouteMatch,
-  >(opts?: {
-    select?: (match: TRouteMatch) => TSelected
-  }): TSelected => {
-    return useMatch({ select: opts?.select, from: this.id })
+  useMatch: UseMatchRoute<TId> = (opts) => {
+    return useMatch({
+      select: opts?.select,
+      from: this.id,
+      structuralSharing: opts?.structuralSharing,
+    } as any) as any
   }
 
-  useRouteContext = <
-    TSelected = Expand<RouteTypesById<TRouter, TId>['allContext']>,
-  >(opts?: {
-    select?: (
-      s: Expand<RouteTypesById<TRouter, TId>['allContext']>,
-    ) => TSelected
-  }): TSelected => {
+  useRouteContext: UseRouteContextRoute<TId> = (opts) => {
     return useMatch({
-      from: this.id,
-      select: (d: any) => (opts?.select ? opts.select(d.context) : d.context),
-    })
+      from: this.id as any,
+      select: (d) => (opts?.select ? opts.select(d.context) : d.context),
+    }) as any
   }
 
-  useSearch = <
-    TSelected = Expand<RouteTypesById<TRouter, TId>['fullSearchSchema']>,
-  >(opts?: {
-    select?: (
-      s: Expand<RouteTypesById<TRouter, TId>['fullSearchSchema']>,
-    ) => TSelected
-  }): TSelected => {
-    return useSearch({ ...opts, from: this.id })
+  useSearch: UseSearchRoute<TId> = (opts) => {
+    return useSearch({
+      select: opts?.select,
+      structuralSharing: opts?.structuralSharing,
+      from: this.id,
+    } as any)
   }
 
-  useParams = <
-    TSelected = Expand<RouteTypesById<TRouter, TId>['allParams']>,
-  >(opts?: {
-    select?: (s: Expand<RouteTypesById<TRouter, TId>['allParams']>) => TSelected
-  }): TSelected => {
-    return useParams({ ...opts, from: this.id })
+  useParams: UseParamsRoute<TId> = (opts) => {
+    return useParams({
+      select: opts?.select,
+      structuralSharing: opts?.structuralSharing,
+      from: this.id,
+    } as any)
   }
 
-  useLoaderDeps = <
-    TSelected = RouteTypesById<TRouter, TId>['loaderDeps'],
-  >(opts?: {
-    select?: (s: RouteTypesById<TRouter, TId>['loaderDeps']) => TSelected
-  }): TSelected => {
+  useLoaderDeps: UseLoaderDepsRoute<TId> = (opts) => {
     return useLoaderDeps({ ...opts, from: this.id, strict: false } as any)
   }
 
-  useLoaderData = <
-    TSelected = RouteTypesById<TRouter, TId>['loaderData'],
-  >(opts?: {
-    select?: (s: RouteTypesById<TRouter, TId>['loaderData']) => TSelected
-  }): TSelected => {
+  useLoaderData: UseLoaderDataRoute<TId> = (opts) => {
     return useLoaderData({ ...opts, from: this.id, strict: false } as any)
   }
 
@@ -1258,74 +1245,43 @@ export class Route<
     return this
   }
 
-  useMatch = <
-    TRouter extends AnyRouter = RegisteredRouter,
-    TRouteTree extends AnyRoute = TRouter['routeTree'],
-    TRouteMatch = MakeRouteMatch<TRouteTree, TId>,
-    TSelected = TRouteMatch,
-  >(opts?: {
-    select?: (match: TRouteMatch) => TSelected
-  }): TSelected => {
-    return useMatch({ ...opts, from: this.id })
+  useMatch: UseMatchRoute<TId> = (opts) => {
+    return useMatch({
+      select: opts?.select,
+      from: this.id,
+      structuralSharing: opts?.structuralSharing,
+    } as any) as any
   }
 
-  useRouteContext = <
-    TSelected = Expand<
-      ResolveAllContext<
-        TParentRoute,
-        TRouterContext,
-        TRouteContextFn,
-        TBeforeLoadFn
-      >
-    >,
-  >(opts?: {
-    select?: (
-      search: Expand<
-        ResolveAllContext<
-          TParentRoute,
-          TRouterContext,
-          TRouteContextFn,
-          TBeforeLoadFn
-        >
-      >,
-    ) => TSelected
-  }): TSelected => {
+  useRouteContext: UseRouteContextRoute<TId> = (opts?) => {
     return useMatch({
       ...opts,
       from: this.id,
-      select: (d: any) => (opts?.select ? opts.select(d.context) : d.context),
-    })
+      select: (d) => (opts?.select ? opts.select(d.context) : d.context),
+    }) as any
   }
 
-  useSearch = <
-    TSelected = Expand<ResolveFullSearchSchema<TParentRoute, TSearchValidator>>,
-  >(opts?: {
-    select?: (
-      search: Expand<ResolveFullSearchSchema<TParentRoute, TSearchValidator>>,
-    ) => TSelected
-  }): TSelected => {
-    return useSearch({ ...opts, from: this.id })
+  useSearch: UseSearchRoute<TId> = (opts) => {
+    return useSearch({
+      select: opts?.select,
+      structuralSharing: opts?.structuralSharing,
+      from: this.id,
+    } as any)
   }
 
-  useParams = <
-    TSelected = Expand<ResolveAllParamsFromParent<TParentRoute, TParams>>,
-  >(opts?: {
-    select?: (
-      search: Expand<ResolveAllParamsFromParent<TParentRoute, TParams>>,
-    ) => TSelected
-  }): TSelected => {
-    return useParams({ ...opts, from: this.id })
+  useParams: UseParamsRoute<TId> = (opts) => {
+    return useParams({
+      select: opts?.select,
+      structuralSharing: opts?.structuralSharing,
+      from: this.id,
+    } as any)
   }
 
-  useLoaderDeps = <TSelected = TLoaderDeps>(opts?: {
-    select?: (s: TLoaderDeps) => TSelected
-  }): TSelected => {
+  useLoaderDeps: UseLoaderDepsRoute<TId> = (opts) => {
     return useLoaderDeps({ ...opts, from: this.id } as any)
   }
 
-  useLoaderData = <TSelected = ResolveLoaderData<TLoaderFn>>(opts?: {
-    select?: (search: ResolveLoaderData<TLoaderFn>) => TSelected
-  }): TSelected => {
+  useLoaderData: UseLoaderDataRoute<TId> = (opts) => {
     return useLoaderData({ ...opts, from: this.id } as any)
   }
 
@@ -1643,7 +1599,7 @@ export function createRouteMask<
 >(
   opts: {
     routeTree: TRouteTree
-  } & ToMaskOptions<Router<TRouteTree, 'never'>, TFrom, TTo>,
+  } & ToMaskOptions<Router<TRouteTree, 'never', boolean>, TFrom, TTo>,
 ): RouteMask<TRouteTree> {
   return opts as any
 }
diff --git a/packages/react-router/src/router.ts b/packages/react-router/src/router.ts
index 24e47c122..a687d6410 100644
--- a/packages/react-router/src/router.ts
+++ b/packages/react-router/src/router.ts
@@ -103,7 +103,7 @@ declare global {
       cleanScripts: () => void
       dehydrated?: any
     }
-    __TSR_ROUTER_CONTEXT__?: React.Context<Router<any, any>>
+    __TSR_ROUTER_CONTEXT__?: React.Context<Router<any, any, any>>
   }
 }
 
@@ -111,7 +111,7 @@ export interface Register {
   // router: Router
 }
 
-export type AnyRouter = Router<any, any, any, any>
+export type AnyRouter = Router<any, any, any, any, any>
 
 export type AnyRouterWithContext<TContext> = Router<
   AnyRouteWithContext<TContext>,
@@ -173,6 +173,7 @@ export type TrailingSlashOption = 'always' | 'never' | 'preserve'
 export interface RouterOptions<
   TRouteTree extends AnyRoute,
   TTrailingSlashOption extends TrailingSlashOption,
+  TDefaultStructuralSharingOption extends boolean = false,
   TDehydrated extends Record<string, any> = Record<string, any>,
   TSerializedError extends Record<string, any> = Record<string, any>,
 > {
@@ -464,6 +465,14 @@ export interface RouterOptions<
      */
     strict?: boolean
   }
+
+  /**
+   * Configures whether structural sharing is enabled by default for fine-grained selectors.
+   *
+   * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultstructuralsharing-property)
+   */
+  defaultStructuralSharing?: TDefaultStructuralSharingOption
+
   /**
    * Configures which URI characters are allowed in path params that would ordinarily be escaped by encodeURIComponent.
    *
@@ -539,12 +548,14 @@ export interface DehydratedRouter {
 export type RouterConstructorOptions<
   TRouteTree extends AnyRoute,
   TTrailingSlashOption extends TrailingSlashOption,
+  TDefaultStructuralSharingOption extends boolean,
   TDehydrated extends Record<string, any>,
   TSerializedError extends Record<string, any>,
 > = Omit<
   RouterOptions<
     TRouteTree,
     TTrailingSlashOption,
+    TDefaultStructuralSharingOption,
     TDehydrated,
     TSerializedError
   >,
@@ -636,6 +647,7 @@ export type RouterListener<TRouterEvent extends RouterEvent> = {
 export function createRouter<
   TRouteTree extends AnyRoute,
   TTrailingSlashOption extends TrailingSlashOption,
+  TDefaultStructuralSharingOption extends boolean,
   TDehydrated extends Record<string, any> = Record<string, any>,
   TSerializedError extends Record<string, any> = Record<string, any>,
 >(
@@ -644,6 +656,7 @@ export function createRouter<
     : RouterConstructorOptions<
         TRouteTree,
         TTrailingSlashOption,
+        TDefaultStructuralSharingOption,
         TDehydrated,
         TSerializedError
       >,
@@ -651,6 +664,7 @@ export function createRouter<
   return new Router<
     TRouteTree,
     TTrailingSlashOption,
+    TDefaultStructuralSharingOption,
     TDehydrated,
     TSerializedError
   >(options)
@@ -666,6 +680,7 @@ type MatchRoutesOpts = {
 export class Router<
   in out TRouteTree extends AnyRoute,
   in out TTrailingSlashOption extends TrailingSlashOption,
+  in out TDefaultStructuralSharingOption extends boolean,
   in out TDehydrated extends Record<string, any> = Record<string, any>,
   in out TSerializedError extends Record<string, any> = Record<string, any>,
 > {
@@ -703,6 +718,7 @@ export class Router<
       RouterOptions<
         TRouteTree,
         TTrailingSlashOption,
+        TDefaultStructuralSharingOption,
         TDehydrated,
         TSerializedError
       >,
@@ -729,6 +745,7 @@ export class Router<
     options: RouterConstructorOptions<
       TRouteTree,
       TTrailingSlashOption,
+      TDefaultStructuralSharingOption,
       TDehydrated,
       TSerializedError
     >,
@@ -760,6 +777,7 @@ export class Router<
     newOptions: RouterConstructorOptions<
       TRouteTree,
       TTrailingSlashOption,
+      TDefaultStructuralSharingOption,
       TDehydrated,
       TSerializedError
     >,
@@ -1660,6 +1678,7 @@ export class Router<
           }
           maskedNext = build(maskedDest)
         }
+        // console.log('buildWithMatches', {foundMask, dest, maskedDest, maskedNext})
       }
 
       const nextMatches = this.getMatchedRoutes(next, dest)
@@ -2209,6 +2228,8 @@ export class Router<
                   let pendingTimeout: ReturnType<typeof setTimeout>
 
                   if (shouldPending) {
+                    console.log('### 2186 setting timeout', pendingMs)
+
                     // If we might show a pending component, we need to wait for the
                     // pending promise to resolve before we start showing that state
                     pendingTimeout = setTimeout(() => {
@@ -2672,7 +2693,13 @@ export class Router<
     TMaskTo extends string = '',
   >(
     opts: NavigateOptions<
-      Router<TRouteTree, TTrailingSlashOption, TDehydrated, TSerializedError>,
+      Router<
+        TRouteTree,
+        TTrailingSlashOption,
+        TDefaultStructuralSharingOption,
+        TDehydrated,
+        TSerializedError
+      >,
       TFrom,
       TTo,
       TMaskFrom,
@@ -2746,7 +2773,13 @@ export class Router<
     TResolved = ResolveRelativePath<TFrom, NoInfer<TTo>>,
   >(
     location: ToOptions<
-      Router<TRouteTree, TTrailingSlashOption, TDehydrated, TSerializedError>,
+      Router<
+        TRouteTree,
+        TTrailingSlashOption,
+        TDefaultStructuralSharingOption,
+        TDehydrated,
+        TSerializedError
+      >,
       TFrom,
       TTo
     >,
diff --git a/packages/react-router/src/routerContext.tsx b/packages/react-router/src/routerContext.tsx
index c6fd19bc2..1e5ed3354 100644
--- a/packages/react-router/src/routerContext.tsx
+++ b/packages/react-router/src/routerContext.tsx
@@ -1,7 +1,7 @@
 import * as React from 'react'
 import type { Router } from './router'
 
-const routerContext = React.createContext<Router<any, any>>(null!)
+const routerContext = React.createContext<Router<any, any, any>>(null!)
 
 export function getRouterContext() {
   if (typeof document === 'undefined') {
diff --git a/packages/react-router/src/structuralSharing.ts b/packages/react-router/src/structuralSharing.ts
new file mode 100644
index 000000000..9921e9c86
--- /dev/null
+++ b/packages/react-router/src/structuralSharing.ts
@@ -0,0 +1,49 @@
+import type { Constrain, ValidateJSON } from './utils'
+import type { AnyRouter } from './router'
+
+export type DefaultStructuralSharingEnabled<TRouter extends AnyRouter> =
+  boolean extends TRouter['options']['defaultStructuralSharing']
+    ? // for now, default to false.
+      // TODO in V2: default to true
+      false
+    : NonNullable<TRouter['options']['defaultStructuralSharing']>
+
+export interface OptionalStructuralSharing<TStructuralSharing, TConstraint> {
+  readonly structuralSharing?:
+    | Constrain<TStructuralSharing, TConstraint>
+    | undefined
+}
+
+export interface RequiredStructuralSharing<TStructuralSharing, TConstraint> {
+  readonly structuralSharing: Constrain<TStructuralSharing, TConstraint>
+}
+
+export type StructuralSharingOption<
+  TRouter extends AnyRouter,
+  TSelected,
+  TStructuralSharing,
+> = unknown extends TSelected
+  ? OptionalStructuralSharing<TStructuralSharing, boolean>
+  : unknown extends TRouter['routeTree']
+    ? OptionalStructuralSharing<TStructuralSharing, boolean>
+    : TSelected extends ValidateJSON<TSelected>
+      ? OptionalStructuralSharing<TStructuralSharing, boolean>
+      : DefaultStructuralSharingEnabled<TRouter> extends true
+        ? RequiredStructuralSharing<TStructuralSharing, false>
+        : OptionalStructuralSharing<TStructuralSharing, false>
+
+export type StructuralSharingEnabled<
+  TRouter extends AnyRouter,
+  TStructuralSharing,
+> = boolean extends TStructuralSharing
+  ? DefaultStructuralSharingEnabled<TRouter>
+  : TStructuralSharing
+
+export type ValidateSelected<
+  TRouter extends AnyRouter,
+  TSelected,
+  TStructuralSharing,
+> =
+  StructuralSharingEnabled<TRouter, TStructuralSharing> extends true
+    ? ValidateJSON<TSelected>
+    : TSelected
diff --git a/packages/react-router/src/useLoaderData.tsx b/packages/react-router/src/useLoaderData.tsx
index bf12d61fb..f174b9313 100644
--- a/packages/react-router/src/useLoaderData.tsx
+++ b/packages/react-router/src/useLoaderData.tsx
@@ -1,45 +1,93 @@
 import { useMatch } from './useMatch'
-import type { RegisteredRouter } from './router'
-import type { AnyRoute } from './route'
-import type { MakeRouteMatch } from './Matches'
-import type { RouteIds } from './routeInfo'
-import type { Constrain, StrictOrFrom } from './utils'
+import type {
+  StructuralSharingOption,
+  ValidateSelected,
+} from './structuralSharing'
+import type { AnyRouter, RegisteredRouter } from './router'
+import type { AllLoaderData, RouteById } from './routeInfo'
+import type { Expand, StrictOrFrom } from './utils'
 
-export type UseLoaderDataOptions<
-  TRouteTree extends AnyRoute,
+export interface UseLoaderDataBaseOptions<
+  TRouter extends AnyRouter,
   TFrom,
   TStrict extends boolean,
-  TRouteMatch extends MakeRouteMatch<TRouteTree, TFrom, TStrict>,
   TSelected,
-> = StrictOrFrom<Constrain<TFrom, RouteIds<TRouteTree>>, TStrict> & {
-  select?: (match: Required<TRouteMatch>['loaderData']) => TSelected
+  TStructuralSharing,
+> {
+  select?: (
+    match: ResolveLoaderData<TRouter, TFrom, TStrict>,
+  ) => ValidateSelected<TRouter, TSelected, TStructuralSharing>
 }
 
+export type UseLoaderDataOptions<
+  TRouter extends AnyRouter,
+  TFrom extends string | undefined,
+  TStrict extends boolean,
+  TSelected,
+  TStructuralSharing,
+> = StrictOrFrom<TRouter, TFrom, TStrict> &
+  UseLoaderDataBaseOptions<
+    TRouter,
+    TFrom,
+    TStrict,
+    TSelected,
+    TStructuralSharing
+  > &
+  StructuralSharingOption<TRouter, TSelected, TStructuralSharing>
+
+export type ResolveLoaderData<
+  TRouter extends AnyRouter,
+  TFrom,
+  TStrict extends boolean,
+> = TStrict extends false
+  ? AllLoaderData<TRouter['routeTree']>
+  : Expand<RouteById<TRouter['routeTree'], TFrom>['types']['loaderData']>
+
+export type UseLoaderDataResult<
+  TRouter extends AnyRouter,
+  TFrom,
+  TStrict extends boolean,
+  TSelected,
+> = unknown extends TSelected
+  ? ResolveLoaderData<TRouter, TFrom, TStrict>
+  : TSelected
+
+export type UseLoaderDataRoute<out TId> = <
+  TRouter extends AnyRouter = RegisteredRouter,
+  TSelected = unknown,
+  TStructuralSharing extends boolean = boolean,
+>(
+  opts?: UseLoaderDataBaseOptions<
+    TRouter,
+    TId,
+    true,
+    TSelected,
+    TStructuralSharing
+  > &
+    StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
+) => UseLoaderDataResult<TRouter, TId, true, TSelected>
+
 export function useLoaderData<
-  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
+  TRouter extends AnyRouter = RegisteredRouter,
   TFrom extends string | undefined = undefined,
   TStrict extends boolean = true,
-  TRouteMatch extends MakeRouteMatch<
-    TRouteTree,
-    TFrom,
-    TStrict
-  > = MakeRouteMatch<TRouteTree, TFrom, TStrict>,
-  TSelected = Required<TRouteMatch>['loaderData'],
+  TSelected = unknown,
+  TStructuralSharing extends boolean = boolean,
 >(
   opts: UseLoaderDataOptions<
-    TRouteTree,
+    TRouter,
     TFrom,
     TStrict,
-    TRouteMatch,
-    TSelected
+    TSelected,
+    TStructuralSharing
   >,
-): TSelected {
-  return useMatch<TRouteTree, TFrom, TStrict, TRouteMatch, TSelected>({
-    ...opts,
-    select: (s) => {
-      return typeof opts.select === 'function'
-        ? opts.select(s.loaderData)
-        : (s.loaderData as TSelected)
+): UseLoaderDataResult<TRouter, TFrom, TStrict, TSelected> {
+  return useMatch({
+    from: opts.from!,
+    strict: opts.strict,
+    structuralSharing: opts.structuralSharing,
+    select: (s: any) => {
+      return opts.select ? opts.select(s.loaderData) : s.loaderData
     },
-  })
+  } as any) as UseLoaderDataResult<TRouter, TFrom, TStrict, TSelected>
 }
diff --git a/packages/react-router/src/useLoaderDeps.tsx b/packages/react-router/src/useLoaderDeps.tsx
index 3a6150280..76f010bc4 100644
--- a/packages/react-router/src/useLoaderDeps.tsx
+++ b/packages/react-router/src/useLoaderDeps.tsx
@@ -1,29 +1,64 @@
 import { useMatch } from './useMatch'
-import type { RegisteredRouter } from './router'
-import type { AnyRoute } from './route'
-import type { MakeRouteMatch } from './Matches'
-import type { RouteIds } from './routeInfo'
-import type { Constrain, StrictOrFrom } from './utils'
+import type {
+  StructuralSharingOption,
+  ValidateSelected,
+} from './structuralSharing'
+import type { AnyRouter, RegisteredRouter } from './router'
+import type { RouteById } from './routeInfo'
+import type { Expand, StrictOrFrom } from './utils'
+
+export interface UseLoaderDepsBaseOptions<
+  TRouter extends AnyRouter,
+  TFrom,
+  TSelected,
+  TStructuralSharing,
+> {
+  select?: (
+    deps: ResolveLoaderDeps<TRouter, TFrom>,
+  ) => ValidateSelected<TRouter, TSelected, TStructuralSharing>
+}
+
+export type UseLoaderDepsOptions<
+  TRouter extends AnyRouter,
+  TFrom extends string | undefined,
+  TSelected,
+  TStructuralSharing,
+> = StrictOrFrom<TRouter, TFrom> &
+  UseLoaderDepsBaseOptions<TRouter, TFrom, TSelected, TStructuralSharing> &
+  StructuralSharingOption<TRouter, TSelected, TStructuralSharing>
+
+export type ResolveLoaderDeps<TRouter extends AnyRouter, TFrom> = Expand<
+  RouteById<TRouter['routeTree'], TFrom>['types']['loaderDeps']
+>
+
+export type UseLoaderDepsResult<
+  TRouter extends AnyRouter,
+  TFrom,
+  TSelected,
+> = unknown extends TSelected ? ResolveLoaderDeps<TRouter, TFrom> : TSelected
+
+export type UseLoaderDepsRoute<out TId> = <
+  TRouter extends AnyRouter = RegisteredRouter,
+  TSelected = unknown,
+  TStructuralSharing extends boolean = boolean,
+>(
+  opts?: UseLoaderDepsBaseOptions<TRouter, TId, TSelected, TStructuralSharing> &
+    StructuralSharingOption<TRouter, TSelected, false>,
+) => UseLoaderDepsResult<TRouter, TId, TSelected>
 
 export function useLoaderDeps<
-  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
+  TRouter extends AnyRouter = RegisteredRouter,
   TFrom extends string | undefined = undefined,
-  TRouteMatch extends MakeRouteMatch<TRouteTree, TFrom> = MakeRouteMatch<
-    TRouteTree,
-    TFrom
-  >,
-  TSelected = Required<TRouteMatch>['loaderDeps'],
+  TSelected = unknown,
+  TStructuralSharing extends boolean = boolean,
 >(
-  opts: StrictOrFrom<Constrain<TFrom, RouteIds<TRouteTree>>> & {
-    select?: (match: TRouteMatch) => TSelected
-  },
-): TSelected {
+  opts: UseLoaderDepsOptions<TRouter, TFrom, TSelected, TStructuralSharing>,
+): UseLoaderDepsResult<TRouter, TFrom, TSelected> {
+  const { select, ...rest } = opts
   return useMatch({
-    ...opts,
+    ...rest,
     select: (s) => {
-      return typeof opts.select === 'function'
-        ? opts.select(s.loaderDeps)
-        : s.loaderDeps
+      return select ? select(s.loaderDeps) : s.loaderDeps
     },
-  })
+  }) as UseLoaderDepsResult<TRouter, TFrom, TSelected>
 }
diff --git a/packages/react-router/src/useLocation.tsx b/packages/react-router/src/useLocation.tsx
index 6da738533..4b4878d70 100644
--- a/packages/react-router/src/useLocation.tsx
+++ b/packages/react-router/src/useLocation.tsx
@@ -1,15 +1,37 @@
 import { useRouterState } from './useRouterState'
+import type {
+  StructuralSharingOption,
+  ValidateSelected,
+} from './structuralSharing'
 import type { AnyRouter, RegisteredRouter, RouterState } from './router'
 
+export interface UseLocationBaseOptions<
+  TRouter extends AnyRouter,
+  TSelected,
+  TStructuralSharing extends boolean = boolean,
+> {
+  select?: (
+    state: RouterState<TRouter['routeTree']>['location'],
+  ) => ValidateSelected<TRouter, TSelected, TStructuralSharing>
+}
+
+export type UseLocationResult<
+  TRouter extends AnyRouter,
+  TSelected,
+> = unknown extends TSelected
+  ? RouterState<TRouter['routeTree']>['location']
+  : TSelected
+
 export function useLocation<
   TRouter extends AnyRouter = RegisteredRouter,
-  TLocationState = RouterState<TRouter['routeTree']>['location'],
-  TSelected = TLocationState,
->(opts?: { select?: (state: TLocationState) => TSelected }): TSelected {
+  TSelected = unknown,
+  TStructuralSharing extends boolean = boolean,
+>(
+  opts?: UseLocationBaseOptions<TRouter, TSelected, TStructuralSharing> &
+    StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
+): UseLocationResult<TRouter, TSelected> {
   return useRouterState({
-    select: (state) =>
-      opts?.select
-        ? opts.select(state.location as TLocationState)
-        : (state.location as TSelected),
-  })
+    select: (state: any) =>
+      opts?.select ? opts.select(state.location) : state.location,
+  } as any) as UseLocationResult<TRouter, TSelected>
 }
diff --git a/packages/react-router/src/useMatch.tsx b/packages/react-router/src/useMatch.tsx
index c912a993a..ac119802b 100644
--- a/packages/react-router/src/useMatch.tsx
+++ b/packages/react-router/src/useMatch.tsx
@@ -2,44 +2,93 @@ import * as React from 'react'
 import invariant from 'tiny-invariant'
 import { useRouterState } from './useRouterState'
 import { matchContext } from './matchContext'
-import type { RegisteredRouter } from './router'
-import type { AnyRoute } from './route'
+import type {
+  StructuralSharingOption,
+  ValidateSelected,
+} from './structuralSharing'
+import type { AnyRouter, RegisteredRouter } from './router'
 import type { MakeRouteMatch } from './Matches'
-import type { RouteIds } from './routeInfo'
-import type { Constrain, StrictOrFrom } from './utils'
+import type { StrictOrFrom, ThrowOrOptional } from './utils'
 
-export type UseMatchOptions<
+export interface UseMatchBaseOptions<
+  TRouter extends AnyRouter,
   TFrom,
   TStrict extends boolean,
-  TRouteMatch,
+  TThrow,
   TSelected,
-  TThrow extends boolean,
-> = StrictOrFrom<TFrom, TStrict> & {
-  select?: (match: TRouteMatch) => TSelected
+  TStructuralSharing extends boolean,
+> {
+  select?: (
+    match: MakeRouteMatch<TRouter['routeTree'], TFrom, TStrict>,
+  ) => ValidateSelected<TRouter, TSelected, TStructuralSharing>
   shouldThrow?: TThrow
 }
 
+export type UseMatchRoute<out TFrom> = <
+  TRouter extends AnyRouter = RegisteredRouter,
+  TSelected = unknown,
+  TStructuralSharing extends boolean = boolean,
+>(
+  opts?: UseMatchBaseOptions<
+    TRouter,
+    TFrom,
+    true,
+    true,
+    TSelected,
+    TStructuralSharing
+  > &
+    StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
+) => UseMatchResult<TRouter, TFrom, true, TSelected>
+
+export type UseMatchOptions<
+  TRouter extends AnyRouter,
+  TFrom extends string | undefined,
+  TStrict extends boolean,
+  TSelected,
+  TThrow extends boolean,
+  TStructuralSharing extends boolean,
+> = StrictOrFrom<TRouter, TFrom, TStrict> &
+  UseMatchBaseOptions<
+    TRouter,
+    TFrom,
+    TStrict,
+    TThrow,
+    TSelected,
+    TStructuralSharing
+  > &
+  StructuralSharingOption<TRouter, TSelected, TStructuralSharing>
+
+export type UseMatchResult<
+  TRouter extends AnyRouter,
+  TFrom,
+  TStrict extends boolean,
+  TSelected,
+> = unknown extends TSelected
+  ? MakeRouteMatch<TRouter['routeTree'], TFrom, TStrict>
+  : TSelected
+
 export function useMatch<
-  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
+  TRouter extends AnyRouter = RegisteredRouter,
   TFrom extends string | undefined = undefined,
   TStrict extends boolean = true,
-  TRouteMatch = MakeRouteMatch<TRouteTree, TFrom, TStrict>,
-  TSelected = TRouteMatch,
   TThrow extends boolean = true,
+  TSelected = unknown,
+  TStructuralSharing extends boolean = boolean,
 >(
   opts: UseMatchOptions<
-    Constrain<TFrom, RouteIds<TRouteTree>>,
+    TRouter,
+    TFrom,
     TStrict,
-    TRouteMatch,
     TSelected,
-    TThrow
+    TThrow,
+    TStructuralSharing
   >,
-): TThrow extends true ? TSelected : TSelected | undefined {
+): ThrowOrOptional<UseMatchResult<TRouter, TFrom, TStrict, TSelected>, TThrow> {
   const nearestMatchId = React.useContext(matchContext)
 
   const matchSelection = useRouterState({
-    select: (state) => {
-      const match = state.matches.find((d) =>
+    select: (state: any) => {
+      const match = state.matches.find((d: any) =>
         opts.from ? opts.from === d.routeId : d.id === nearestMatchId,
       )
       invariant(
@@ -51,9 +100,10 @@ export function useMatch<
         return undefined
       }
 
-      return opts.select ? opts.select(match as any) : match
+      return opts.select ? opts.select(match) : match
     },
-  })
+    structuralSharing: opts.structuralSharing,
+  } as any)
 
-  return matchSelection as TSelected
+  return matchSelection as any
 }
diff --git a/packages/react-router/src/useParams.tsx b/packages/react-router/src/useParams.tsx
index 1790c7321..bfa19e0cd 100644
--- a/packages/react-router/src/useParams.tsx
+++ b/packages/react-router/src/useParams.tsx
@@ -1,38 +1,87 @@
 import { useMatch } from './useMatch'
-import type { AnyRoute } from './route'
-import type { AllParams, RouteById, RouteIds } from './routeInfo'
-import type { RegisteredRouter } from './router'
-import type { Constrain, StrictOrFrom } from './utils'
+import type {
+  StructuralSharingOption,
+  ValidateSelected,
+} from './structuralSharing'
+import type { AllParams, RouteById } from './routeInfo'
+import type { AnyRouter, RegisteredRouter } from './router'
+import type { Expand, StrictOrFrom } from './utils'
 
-export type UseParamsOptions<
+export interface UseParamsBaseOptions<
+  TRouter extends AnyRouter,
   TFrom,
   TStrict extends boolean,
-  TParams,
   TSelected,
-> = StrictOrFrom<TFrom, TStrict> & {
-  select?: (params: TParams) => TSelected
+  TStructuralSharing,
+> {
+  select?: (
+    params: ResolveParams<TRouter, TFrom, TStrict>,
+  ) => ValidateSelected<TRouter, TSelected, TStructuralSharing>
 }
 
+export type UseParamsOptions<
+  TRouter extends AnyRouter,
+  TFrom extends string | undefined,
+  TStrict extends boolean,
+  TSelected,
+  TStructuralSharing,
+> = StrictOrFrom<TRouter, TFrom, TStrict> &
+  UseParamsBaseOptions<TRouter, TFrom, TStrict, TSelected, TStructuralSharing> &
+  StructuralSharingOption<TRouter, TSelected, TStructuralSharing>
+
+export type ResolveParams<
+  TRouter extends AnyRouter,
+  TFrom,
+  TStrict extends boolean,
+> = TStrict extends false
+  ? AllParams<TRouter['routeTree']>
+  : Expand<RouteById<TRouter['routeTree'], TFrom>['types']['allParams']>
+
+export type UseParamsResult<
+  TRouter extends AnyRouter,
+  TFrom,
+  TStrict extends boolean,
+  TSelected,
+> = unknown extends TSelected
+  ? ResolveParams<TRouter, TFrom, TStrict>
+  : TSelected
+
+export type UseParamsRoute<out TFrom> = <
+  TRouter extends AnyRouter = RegisteredRouter,
+  TSelected = unknown,
+  TStructuralSharing extends boolean = boolean,
+>(
+  opts?: UseParamsBaseOptions<
+    TRouter,
+    TFrom,
+    true,
+    TSelected,
+    TStructuralSharing
+  > &
+    StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
+) => UseParamsResult<TRouter, TFrom, true, TSelected>
+
 export function useParams<
-  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
+  TRouter extends AnyRouter = RegisteredRouter,
   TFrom extends string | undefined = undefined,
   TStrict extends boolean = true,
-  TParams = TStrict extends false
-    ? AllParams<TRouteTree>
-    : RouteById<TRouteTree, TFrom>['types']['allParams'],
-  TSelected = TParams,
+  TSelected = unknown,
+  TStructuralSharing extends boolean = boolean,
 >(
   opts: UseParamsOptions<
-    Constrain<TFrom, RouteIds<TRouteTree>>,
+    TRouter,
+    TFrom,
     TStrict,
-    TParams,
-    TSelected
+    TSelected,
+    TStructuralSharing
   >,
-): TSelected {
+): UseParamsResult<TRouter, TFrom, TStrict, TSelected> {
   return useMatch({
-    ...opts,
-    select: (match) => {
-      return opts.select ? opts.select(match.params as TParams) : match.params
+    from: opts.from!,
+    strict: opts.strict,
+    structuralSharing: opts.structuralSharing,
+    select: (match: any) => {
+      return opts.select ? opts.select(match.params) : match.params
     },
-  }) as TSelected
+  } as any) as UseParamsResult<TRouter, TFrom, TStrict, TSelected>
 }
diff --git a/packages/react-router/src/useRouteContext.ts b/packages/react-router/src/useRouteContext.ts
index cbbcb7dc0..084354026 100644
--- a/packages/react-router/src/useRouteContext.ts
+++ b/packages/react-router/src/useRouteContext.ts
@@ -1,38 +1,60 @@
 import { useMatch } from './useMatch'
-import type { MakeRouteMatch } from './Matches'
-import type { AnyRoute } from './route'
-import type { AllContext, RouteById, RouteIds } from './routeInfo'
-import type { RegisteredRouter } from './router'
-import type { Constrain, Expand, StrictOrFrom } from './utils'
+import type { AllContext, RouteById } from './routeInfo'
+import type { AnyRouter, RegisteredRouter } from './router'
+import type { Expand, StrictOrFrom } from './utils'
 
-export type UseRouteContextOptions<
+export interface UseRouteContextBaseOptions<
+  TRouter extends AnyRouter,
   TFrom,
   TStrict extends boolean,
-  TRouteContext,
   TSelected,
-> = StrictOrFrom<TFrom, TStrict> & {
-  select?: (search: TRouteContext) => TSelected
+> {
+  select?: (search: ResolveRouteContext<TRouter, TFrom, TStrict>) => TSelected
 }
 
+export type UseRouteContextOptions<
+  TRouter extends AnyRouter,
+  TFrom extends string | undefined,
+  TStrict extends boolean,
+  TSelected,
+> = StrictOrFrom<TRouter, TFrom, TStrict> &
+  UseRouteContextBaseOptions<TRouter, TFrom, TStrict, TSelected>
+
+export type ResolveRouteContext<
+  TRouter extends AnyRouter,
+  TFrom,
+  TStrict extends boolean,
+> = TStrict extends false
+  ? AllContext<TRouter['routeTree']>
+  : Expand<RouteById<TRouter['routeTree'], TFrom>['types']['allContext']>
+
+export type UseRouteContextResult<
+  TRouter extends AnyRouter,
+  TFrom,
+  TStrict extends boolean,
+  TSelected,
+> = unknown extends TSelected
+  ? ResolveRouteContext<TRouter, TFrom, TStrict>
+  : TSelected
+
+export type UseRouteContextRoute<out TFrom> = <
+  TRouter extends AnyRouter = RegisteredRouter,
+  TSelected = unknown,
+>(
+  opts?: UseRouteContextBaseOptions<TRouter, TFrom, true, TSelected>,
+) => UseRouteContextResult<TRouter, TFrom, true, TSelected>
+
 export function useRouteContext<
-  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
+  TRouter extends AnyRouter = RegisteredRouter,
   TFrom extends string | undefined = undefined,
   TStrict extends boolean = true,
-  TRouteContext = TStrict extends false
-    ? AllContext<TRouteTree>
-    : Expand<RouteById<TRouteTree, TFrom>['types']['allContext']>,
-  TSelected = TRouteContext,
+  TSelected = unknown,
 >(
-  opts: UseRouteContextOptions<
-    Constrain<TFrom, RouteIds<TRouteTree>>,
-    TStrict,
-    TRouteContext,
-    TSelected
-  >,
-): TSelected {
+  opts: UseRouteContextOptions<TRouter, TFrom, TStrict, TSelected>,
+): UseRouteContextResult<TRouter, TFrom, TStrict, TSelected> {
   return useMatch({
     ...(opts as any),
-    select: (match: MakeRouteMatch<TRouteTree, TFrom>) =>
+    select: (match) =>
       opts.select ? opts.select(match.context) : match.context,
-  })
+  }) as UseRouteContextResult<TRouter, TFrom, TStrict, TSelected>
 }
diff --git a/packages/react-router/src/useRouterState.tsx b/packages/react-router/src/useRouterState.tsx
index a484902fc..9351aff23 100644
--- a/packages/react-router/src/useRouterState.tsx
+++ b/packages/react-router/src/useRouterState.tsx
@@ -1,16 +1,55 @@
 import { useStore } from '@tanstack/react-store'
+import { useRef } from 'react'
 import { useRouter } from './useRouter'
+import { replaceEqualDeep } from './utils'
+import type {
+  StructuralSharingOption,
+  ValidateSelected,
+} from './structuralSharing'
 import type { AnyRouter, RegisteredRouter, RouterState } from './router'
 
+export type UseRouterStateOptions<
+  TRouter extends AnyRouter,
+  TSelected,
+  TStructuralSharing,
+> = {
+  router?: TRouter
+  select?: (
+    state: RouterState<TRouter['routeTree']>,
+  ) => ValidateSelected<TRouter, TSelected, TStructuralSharing>
+} & StructuralSharingOption<TRouter, TSelected, TStructuralSharing>
+
+export type UseRouterStateResult<
+  TRouter extends AnyRouter,
+  TSelected,
+> = unknown extends TSelected ? RouterState<TRouter['routeTree']> : TSelected
+
 export function useRouterState<
   TRouter extends AnyRouter = RegisteredRouter,
-  TSelected = RouterState<TRouter['routeTree']>,
->(opts?: {
-  router?: TRouter
-  select: (state: RouterState<RegisteredRouter['routeTree']>) => TSelected
-}): TSelected {
+  TSelected = unknown,
+  TStructuralSharing extends boolean = boolean,
+>(
+  opts?: UseRouterStateOptions<TRouter, TSelected, TStructuralSharing>,
+): UseRouterStateResult<TRouter, TSelected> {
   const contextRouter = useRouter<TRouter>({
     warn: opts?.router === undefined,
   })
-  return useStore((opts?.router || contextRouter).__store, opts?.select as any)
+  const router = opts?.router || contextRouter
+  const previousResult =
+    useRef<ValidateSelected<TRouter, TSelected, TStructuralSharing>>()
+
+  return useStore(router.__store, (state) => {
+    if (opts?.select) {
+      if (opts.structuralSharing ?? router.options.defaultStructuralSharing) {
+        const newSlice = replaceEqualDeep(
+          previousResult.current,
+          opts.select(state),
+        )
+        previousResult.current = newSlice
+        return newSlice
+      }
+      return opts.select(state)
+    }
+    return state
+  }) as UseRouterStateResult<TRouter, TSelected>
 }
diff --git a/packages/react-router/src/useSearch.tsx b/packages/react-router/src/useSearch.tsx
index 0f98c57b2..53299d58f 100644
--- a/packages/react-router/src/useSearch.tsx
+++ b/packages/react-router/src/useSearch.tsx
@@ -1,38 +1,87 @@
 import { useMatch } from './useMatch'
-import type { AnyRoute } from './route'
-import type { FullSearchSchema, RouteById, RouteIds } from './routeInfo'
-import type { RegisteredRouter } from './router'
-import type { Constrain, Expand, StrictOrFrom } from './utils'
+import type {
+  StructuralSharingOption,
+  ValidateSelected,
+} from './structuralSharing'
+import type { FullSearchSchema, RouteById } from './routeInfo'
+import type { AnyRouter, RegisteredRouter } from './router'
+import type { Expand, StrictOrFrom } from './utils'
 
-export type UseSearchOptions<
+export interface UseSearchBaseOptions<
+  TRouter extends AnyRouter,
   TFrom,
   TStrict extends boolean,
-  TSearch,
   TSelected,
-> = StrictOrFrom<TFrom, TStrict> & {
-  select?: (search: TSearch) => TSelected
+  TStructuralSharing,
+> {
+  select?: (
+    state: ResolveSearch<TRouter, TFrom, TStrict>,
+  ) => ValidateSelected<TRouter, TSelected, TStructuralSharing>
 }
 
+export type UseSearchOptions<
+  TRouter extends AnyRouter,
+  TFrom,
+  TStrict extends boolean,
+  TSelected,
+  TStructuralSharing,
+> = StrictOrFrom<TRouter, TFrom, TStrict> &
+  UseSearchBaseOptions<TRouter, TFrom, TStrict, TSelected, TStructuralSharing> &
+  StructuralSharingOption<TRouter, TSelected, TStructuralSharing>
+
+export type UseSearchResult<
+  TRouter extends AnyRouter,
+  TFrom,
+  TStrict extends boolean,
+  TSelected,
+> = unknown extends TSelected
+  ? ResolveSearch<TRouter, TFrom, TStrict>
+  : TSelected
+
+export type ResolveSearch<
+  TRouter extends AnyRouter,
+  TFrom,
+  TStrict extends boolean,
+> = TStrict extends false
+  ? FullSearchSchema<TRouter['routeTree']>
+  : Expand<RouteById<TRouter['routeTree'], TFrom>['types']['fullSearchSchema']>
+
+export type UseSearchRoute<out TFrom> = <
+  TRouter extends AnyRouter = RegisteredRouter,
+  TSelected = unknown,
+  TStructuralSharing extends boolean = boolean,
+>(
+  opts?: UseSearchBaseOptions<
+    TRouter,
+    TFrom,
+    true,
+    TSelected,
+    TStructuralSharing
+  > &
+    StructuralSharingOption<TRouter, TSelected, TStructuralSharing>,
+) => UseSearchResult<TRouter, TFrom, true, TSelected>
+
 export function useSearch<
-  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
+  TRouter extends AnyRouter = RegisteredRouter,
   TFrom extends string | undefined = undefined,
   TStrict extends boolean = true,
-  TSearch = TStrict extends false
-    ? FullSearchSchema<TRouteTree>
-    : Expand<RouteById<TRouteTree, TFrom>['types']['fullSearchSchema']>,
-  TSelected = TSearch,
+  TSelected = unknown,
+  TStructuralSharing extends boolean = boolean,
 >(
   opts: UseSearchOptions<
-    Constrain<TFrom, RouteIds<TRouteTree>>,
+    TRouter,
+    TFrom,
     TStrict,
-    TSearch,
-    TSelected
+    TSelected,
+    TStructuralSharing
   >,
-): TSelected {
+): UseSearchResult<TRouter, TFrom, TStrict, TSelected> {
   return useMatch({
-    ...opts,
-    select: (match) => {
+    from: opts.from!,
+    strict: opts.strict,
+    structuralSharing: opts.structuralSharing,
+    select: (match: any) => {
       return opts.select ? opts.select(match.search) : match.search
     },
-  })
+  }) as UseSearchResult<TRouter, TFrom, TStrict, TSelected>
 }
diff --git a/packages/react-router/src/utils.ts b/packages/react-router/src/utils.ts
index a7301d3ac..f650e81bc 100644
--- a/packages/react-router/src/utils.ts
+++ b/packages/react-router/src/utils.ts
@@ -1,4 +1,6 @@
 import * as React from 'react'
+import type { RouteIds } from './routeInfo'
+import type { AnyRouter } from './router'
 
 export type NoInfer<T> = [T][T extends any ? 0 : never]
 export type IsAny<TValue, TYesResult, TNoResult = TValue> = 1 extends 0 & TValue
@@ -103,10 +105,16 @@ export type MergeUnion<TUnion> =
   | MergeUnionPrimitives<TUnion>
   | MergeUnionObject<TUnion>
 
-export type Constrain<T, TConstaint, TDefault = TConstaint> =
-  | (T extends TConstaint ? T : never)
+export type Constrain<T, TConstraint, TDefault = TConstraint> =
+  | (T extends TConstraint ? T : never)
   | TDefault
 
+export type ValidateJSON<T> = ((...args: Array<any>) => any) extends T
+  ? unknown extends T
+    ? never
+    : 'Function is not serializable'
+  : { [K in keyof T]: ValidateJSON<T[K]> }
+
 export function last<T>(arr: Array<T>) {
   return arr[arr.length - 1]
 }
@@ -308,6 +316,7 @@ export type StringLiteral<T> = T extends string
   : never
 
 export type StrictOrFrom<
+  TRouter extends AnyRouter,
   TFrom,
   TStrict extends boolean = true,
 > = TStrict extends false
@@ -316,10 +325,14 @@ export type StrictOrFrom<
       strict: TStrict
     }
   : {
-      from: StringLiteral<TFrom> | TFrom
+      from: StringLiteral<Constrain<TFrom, RouteIds<TRouter['routeTree']>>>
       strict?: TStrict
     }
 
+export type ThrowOrOptional<T, TThrow extends boolean> = TThrow extends true
+  ? T
+  : T | undefined
+
 export const useLayoutEffect =
   typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect
 
diff --git a/packages/start/src/client/Meta.tsx b/packages/start/src/client/Meta.tsx
index b7a2accd2..2c87dede0 100644
--- a/packages/start/src/client/Meta.tsx
+++ b/packages/start/src/client/Meta.tsx
@@ -68,6 +68,7 @@ export const useMeta = () => {
             ...link,
           },
         })) as Array<RouterManagedTag>,
+    structuralSharing: true as any,
   })
 
   const preloadMeta = useRouterState({
@@ -92,6 +93,7 @@ export const useMeta = () => {
 
       return preloadMeta
     },
+    structuralSharing: true as any,
   })
 
   return uniqBy(
diff --git a/packages/start/src/client/Scripts.tsx b/packages/start/src/client/Scripts.tsx
index 4225dbed9..aedd37a67 100644
--- a/packages/start/src/client/Scripts.tsx
+++ b/packages/start/src/client/Scripts.tsx
@@ -25,6 +25,7 @@ export const Scripts = () => {
 
       return assetScripts
     },
+    structuralSharing: true as any,
   })
 
   const { scripts } = useRouterState({


Test patch:
diff --git a/e2e/react-router/basic-file-based/src/routeTree.gen.ts b/e2e/react-router/basic-file-based/src/routeTree.gen.ts
index 9795d4e1b..67daf0e61 100644
--- a/e2e/react-router/basic-file-based/src/routeTree.gen.ts
+++ b/e2e/react-router/basic-file-based/src/routeTree.gen.ts
@@ -17,6 +17,7 @@ import { Route as PostsImport } from './routes/posts'
 import { Route as LayoutImport } from './routes/_layout'
 import { Route as IndexImport } from './routes/index'
 import { Route as PostsIndexImport } from './routes/posts.index'
+import { Route as StructuralSharingEnabledImport } from './routes/structural-sharing.$enabled'
 import { Route as PostsPostIdImport } from './routes/posts.$postId'
 import { Route as LayoutLayout2Import } from './routes/_layout/_layout-2'
 import { Route as groupLazyinsideImport } from './routes/(group)/lazyinside'
@@ -63,6 +64,12 @@ const PostsIndexRoute = PostsIndexImport.update({
   getParentRoute: () => PostsRoute,
 } as any)
 
+const StructuralSharingEnabledRoute = StructuralSharingEnabledImport.update({
+  id: '/structural-sharing/$enabled',
+  path: '/structural-sharing/$enabled',
+  getParentRoute: () => rootRoute,
+} as any)
+
 const PostsPostIdRoute = PostsPostIdImport.update({
   id: '/$postId',
   path: '/$postId',
@@ -204,6 +211,13 @@ declare module '@tanstack/react-router' {
       preLoaderRoute: typeof PostsPostIdImport
       parentRoute: typeof PostsImport
     }
+    '/structural-sharing/$enabled': {
+      id: '/structural-sharing/$enabled'
+      path: '/structural-sharing/$enabled'
+      fullPath: '/structural-sharing/$enabled'
+      preLoaderRoute: typeof StructuralSharingEnabledImport
+      parentRoute: typeof rootRoute
+    }
     '/posts/': {
       id: '/posts/'
       path: '/'
@@ -324,6 +338,7 @@ export interface FileRoutesByFullPath {
   '/inside': typeof groupInsideRoute
   '/lazyinside': typeof groupLazyinsideRoute
   '/posts/$postId': typeof PostsPostIdRoute
+  '/structural-sharing/$enabled': typeof StructuralSharingEnabledRoute
   '/posts/': typeof PostsIndexRoute
   '/insidelayout': typeof groupLayoutInsidelayoutRoute
   '/subfolder/inside': typeof groupSubfolderInsideRoute
@@ -339,6 +354,7 @@ export interface FileRoutesByTo {
   '/inside': typeof groupInsideRoute
   '/lazyinside': typeof groupLazyinsideRoute
   '/posts/$postId': typeof PostsPostIdRoute
+  '/structural-sharing/$enabled': typeof StructuralSharingEnabledRoute
   '/posts': typeof PostsIndexRoute
   '/insidelayout': typeof groupLayoutInsidelayoutRoute
   '/subfolder/inside': typeof groupSubfolderInsideRoute
@@ -359,6 +375,7 @@ export interface FileRoutesById {
   '/(group)/lazyinside': typeof groupLazyinsideRoute
   '/_layout/_layout-2': typeof LayoutLayout2RouteWithChildren
   '/posts/$postId': typeof PostsPostIdRoute
+  '/structural-sharing/$enabled': typeof StructuralSharingEnabledRoute
   '/posts/': typeof PostsIndexRoute
   '/(group)/_layout/insidelayout': typeof groupLayoutInsidelayoutRoute
   '/(group)/subfolder/inside': typeof groupSubfolderInsideRoute
@@ -377,6 +394,7 @@ export interface FileRouteTypes {
     | '/inside'
     | '/lazyinside'
     | '/posts/$postId'
+    | '/structural-sharing/$enabled'
     | '/posts/'
     | '/insidelayout'
     | '/subfolder/inside'
@@ -391,6 +409,7 @@ export interface FileRouteTypes {
     | '/inside'
     | '/lazyinside'
     | '/posts/$postId'
+    | '/structural-sharing/$enabled'
     | '/posts'
     | '/insidelayout'
     | '/subfolder/inside'
@@ -409,6 +428,7 @@ export interface FileRouteTypes {
     | '/(group)/lazyinside'
     | '/_layout/_layout-2'
     | '/posts/$postId'
+    | '/structural-sharing/$enabled'
     | '/posts/'
     | '/(group)/_layout/insidelayout'
     | '/(group)/subfolder/inside'
@@ -424,6 +444,7 @@ export interface RootRouteChildren {
   PostsRoute: typeof PostsRouteWithChildren
   anotherGroupOnlyrouteinsideRoute: typeof anotherGroupOnlyrouteinsideRoute
   groupRoute: typeof groupRouteWithChildren
+  StructuralSharingEnabledRoute: typeof StructuralSharingEnabledRoute
   PostsPostIdEditRoute: typeof PostsPostIdEditRoute
 }
 
@@ -433,6 +454,7 @@ const rootRouteChildren: RootRouteChildren = {
   PostsRoute: PostsRouteWithChildren,
   anotherGroupOnlyrouteinsideRoute: anotherGroupOnlyrouteinsideRoute,
   groupRoute: groupRouteWithChildren,
+  StructuralSharingEnabledRoute: StructuralSharingEnabledRoute,
   PostsPostIdEditRoute: PostsPostIdEditRoute,
 }
 
@@ -451,6 +473,7 @@ export const routeTree = rootRoute
         "/posts",
         "/(another-group)/onlyrouteinside",
         "/(group)",
+        "/structural-sharing/$enabled",
         "/posts_/$postId/edit"
       ]
     },
@@ -509,6 +532,9 @@ export const routeTree = rootRoute
       "filePath": "posts.$postId.tsx",
       "parent": "/posts"
     },
+    "/structural-sharing/$enabled": {
+      "filePath": "structural-sharing.$enabled.tsx"
+    },
     "/posts/": {
       "filePath": "posts.index.tsx",
       "parent": "/posts"
diff --git a/e2e/react-router/basic-file-based/src/routes/structural-sharing.$enabled.tsx b/e2e/react-router/basic-file-based/src/routes/structural-sharing.$enabled.tsx
new file mode 100644
index 000000000..4a477f7cc
--- /dev/null
+++ b/e2e/react-router/basic-file-based/src/routes/structural-sharing.$enabled.tsx
@@ -0,0 +1,84 @@
+import {
+  Link,
+  createFileRoute,
+  getRouteApi,
+  useSearch,
+} from '@tanstack/react-router'
+import React from 'react'
+import { z } from 'zod'
+
+const enabledSchema = {
+  enabled: z.preprocess((val) => {
+    if (typeof val === 'string') {
+      if (val.toLowerCase() === 'true') return true
+      if (val.toLowerCase() === 'false') return false
+    }
+    return val
+  }, z.boolean()),
+}
+
+export const Route = createFileRoute('/structural-sharing/$enabled')({
+  component: RouteComponent,
+  params: {
+    parse: (p) => z.object(enabledSchema).parse(p),
+  },
+  validateSearch: z.object({ foo: z.string(), bar: z.string() }),
+})
+
+function useRenderCount() {
+  const count = React.useRef(0)
+  count.current++
+  return count.current
+}
+
+const api = getRouteApi('/structural-sharing/$enabled')
+function RouteComponent() {
+  console.log('rendering')
+  const renderCount = useRenderCount()
+  const { enabled } = Route.useParams()
+
+  const searchViaRouteHook = Route.useSearch({
+    select: ({ foo, bar }) => {
+      return { values: [foo, bar] }
+    },
+    structuralSharing: enabled,
+  })
+
+  const searchViaHook = useSearch({
+    from: Route.id,
+    select: ({ foo, bar }) => {
+      return { values: [foo, bar] }
+    },
+    structuralSharing: enabled,
+  })
+
+  const searchViaRouteApiHook = api.useSearch({
+    select: ({ foo, bar }) => {
+      return { values: [foo, bar] }
+    },
+    structuralSharing: enabled,
+  })
+
+  return (
+    <>
+      <div data-testid="enabled">{JSON.stringify(enabled)}</div>
+      <div data-testid="render-count">{renderCount}</div>
+      <div data-testid="search-via-hook">{JSON.stringify(searchViaHook)}</div>
+      <div data-testid="search-via-route-hook">
+        {JSON.stringify(searchViaRouteHook)}
+      </div>
+      <div data-testid="search-via-route-api-hook">
+        {JSON.stringify(searchViaRouteApiHook)}
+      </div>
+
+      <br />
+      <Link
+        search={{ foo: 'f2', bar: 'b2' }}
+        from="/structural-sharing/$enabled"
+        data-testid="link"
+      >
+        go to f2/b2
+      </Link>
+    </>
+  )
+}
diff --git a/e2e/react-router/basic-file-based/tests/app.spec.ts b/e2e/react-router/basic-file-based/tests/app.spec.ts
index 1f8893904..de29539c6 100644
--- a/e2e/react-router/basic-file-based/tests/app.spec.ts
+++ b/e2e/react-router/basic-file-based/tests/app.spec.ts
@@ -1,4 +1,5 @@
 import { expect, test } from '@playwright/test'
+import type { Page } from '@playwright/test'
 
 test.beforeEach(async ({ page }) => {
   await page.goto('/')
@@ -78,3 +79,42 @@ test('navigating to an unnested route', async ({ page }) => {
   await expect(page.getByTestId('params-via-route-hook')).toContainText(postId)
   await expect(page.getByTestId('params-via-route-api')).toContainText(postId)
 })
+
+async function getRenderCount(page: Page) {
+  const renderCount = parseInt(
+    await page.getByTestId('render-count').innerText(),
+  )
+  return renderCount
+}
+async function structuralSharingTest(page: Page, enabled: boolean) {
+  page.goto(`/structural-sharing/${enabled}/?foo=f1&bar=b1`)
+  await expect(page.getByTestId('enabled')).toHaveText(JSON.stringify(enabled))
+
+  async function checkSearch({ foo, bar }: { foo: string; bar: string }) {
+    expect(page.url().endsWith(`?foo=${foo}&bar=${bar}`)).toBe(true)
+    const expectedSearch = JSON.stringify({ values: [foo, bar] })
+    await expect(page.getByTestId('search-via-hook')).toHaveText(expectedSearch)
+    await expect(page.getByTestId('search-via-route-hook')).toHaveText(
+      expectedSearch,
+    )
+    await expect(page.getByTestId('search-via-route-api-hook')).toHaveText(
+      expectedSearch,
+    )
+  }
+
+  await checkSearch({ bar: 'b1', foo: 'f1' })
+  await page.getByTestId('link').click()
+  await checkSearch({ bar: 'b2', foo: 'f2' })
+}
+
+test('structural sharing disabled', async ({ page }) => {
+  await structuralSharingTest(page, false)
+  expect(await getRenderCount(page)).toBeGreaterThan(2)
+})
+
+test('structural sharing enabled', async ({ page }) => {
+  await structuralSharingTest(page, true)
+  expect(await getRenderCount(page)).toBe(2)
+  await page.getByTestId('link').click()
+  expect(await getRenderCount(page)).toBe(2)
+})
diff --git a/packages/react-router/tests/route.test-d.tsx b/packages/react-router/tests/route.test-d.tsx
index 372e0e072..d7ad59484 100644
--- a/packages/react-router/tests/route.test-d.tsx
+++ b/packages/react-router/tests/route.test-d.tsx
@@ -100,7 +100,6 @@ test('when creating the root with a loader', () => {
 
 test('when creating the root route with context and routeContext', () => {
   const createRouteResult = createRootRouteWithContext<{ userId: string }>()
-
   const rootRoute = createRouteResult({
     context: (opts) => {
       expectTypeOf(opts).toEqualTypeOf<{
@@ -122,15 +121,21 @@ test('when creating the root route with context and routeContext', () => {
   expectTypeOf(rootRoute.id).toEqualTypeOf<'__root__'>()
   expectTypeOf(rootRoute.path).toEqualTypeOf<'/'>()
 
-  expectTypeOf(rootRoute.useRouteContext()).toEqualTypeOf<{
+  // eslint-disable-next-line unused-imports/no-unused-vars
+  const router = createRouter({
+    routeTree: rootRoute,
+    context: { userId: '123' },
+  })
+
+  expectTypeOf(rootRoute.useRouteContext<typeof router>()).toEqualTypeOf<{
     userId: string
   }>()
 
-  expectTypeOf(rootRoute.useRouteContext<string>)
+  expectTypeOf(rootRoute.useRouteContext<typeof router>)
     .parameter(0)
-    .toEqualTypeOf<
-      { select?: (search: { userId: string }) => string } | undefined
-    >()
+    .exclude<undefined>()
+    .toHaveProperty('select')
+    .toEqualTypeOf<((context: { userId: string }) => unknown) | undefined>()
 })
 
 test('when creating the root route with context and beforeLoad', () => {
@@ -157,15 +162,21 @@ test('when creating the root route with context and beforeLoad', () => {
   expectTypeOf(rootRoute.id).toEqualTypeOf<'__root__'>()
   expectTypeOf(rootRoute.path).toEqualTypeOf<'/'>()
 
-  expectTypeOf(rootRoute.useRouteContext()).toEqualTypeOf<{
+  // eslint-disable-next-line unused-imports/no-unused-vars
+  const router = createRouter({
+    routeTree: rootRoute,
+    context: { userId: '123' },
+  })
+
+  expectTypeOf(rootRoute.useRouteContext<typeof router>()).toEqualTypeOf<{
     userId: string
   }>()
 
-  expectTypeOf(rootRoute.useRouteContext<string>)
+  expectTypeOf(rootRoute.useRouteContext<typeof router>)
     .parameter(0)
-    .toEqualTypeOf<
-      { select?: (search: { userId: string }) => string } | undefined
-    >()
+    .exclude<undefined>()
+    .toHaveProperty('select')
+    .toEqualTypeOf<((context: { userId: string }) => unknown) | undefined>()
 })
 
 test('when creating the root route with context and a loader', () => {
@@ -192,15 +203,21 @@ test('when creating the root route with context and a loader', () => {
   expectTypeOf(rootRoute.id).toEqualTypeOf<'__root__'>()
   expectTypeOf(rootRoute.path).toEqualTypeOf<'/'>()
 
-  expectTypeOf(rootRoute.useRouteContext()).toEqualTypeOf<{
+  // eslint-disable-next-line unused-imports/no-unused-vars
+  const router = createRouter({
+    routeTree: rootRoute,
+    context: { userId: '123' },
+  })
+
+  expectTypeOf(rootRoute.useRouteContext<typeof router>()).toEqualTypeOf<{
     userId: string
   }>()
 
-  expectTypeOf(rootRoute.useRouteContext<string>)
+  expectTypeOf(rootRoute.useRouteContext<typeof router>)
     .parameter(0)
-    .toEqualTypeOf<
-      { select?: (search: { userId: string }) => string } | undefined
-    >()
+    .exclude<undefined>()
+    .toHaveProperty('select')
+    .toEqualTypeOf<((context: { userId: string }) => unknown) | undefined>()
 })
 
 test('when creating the root route with context, routeContext, beforeLoad and a loader', () => {
@@ -260,22 +277,28 @@ test('when creating the root route with context, routeContext, beforeLoad and a
   expectTypeOf(rootRoute.id).toEqualTypeOf<'__root__'>()
   expectTypeOf(rootRoute.path).toEqualTypeOf<'/'>()
 
-  expectTypeOf(rootRoute.useRouteContext()).toEqualTypeOf<{
+  // eslint-disable-next-line unused-imports/no-unused-vars
+  const router = createRouter({
+    routeTree: rootRoute,
+    context: { userId: '123' },
+  })
+
+  expectTypeOf(rootRoute.useRouteContext<typeof router>()).toEqualTypeOf<{
     userId: string
     permission: 'view'
     env: 'env1'
   }>()
 
-  expectTypeOf(rootRoute.useRouteContext<string>)
+  expectTypeOf(rootRoute.useRouteContext<typeof router, string>)
     .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('select')
     .toEqualTypeOf<
-      | {
-          select?: (search: {
-            userId: string
-            permission: 'view'
-            env: 'env1'
-          }) => string
-        }
+      | ((context: {
+          userId: string
+          permission: 'view'
+          env: 'env1'
+        }) => string)
       | undefined
     >()
 })
@@ -301,15 +324,21 @@ test('when creating a child route from the root route with context', () => {
     getParentRoute: () => rootRoute,
   })
 
-  expectTypeOf(rootRoute.useRouteContext()).toEqualTypeOf<{
+  // eslint-disable-next-line unused-imports/no-unused-vars
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([invoicesRoute]),
+    context: { userId: '123' },
+  })
+
+  expectTypeOf(rootRoute.useRouteContext<typeof router>()).toEqualTypeOf<{
     userId: string
   }>()
 
-  expectTypeOf(invoicesRoute.useRouteContext<string>)
+  expectTypeOf(rootRoute.useRouteContext<typeof router>)
     .parameter(0)
-    .toEqualTypeOf<
-      { select?: (search: { userId: string }) => string } | undefined
-    >()
+    .exclude<undefined>()
+    .toHaveProperty('select')
+    .toEqualTypeOf<((context: { userId: string }) => unknown) | undefined>()
 })
 
 test('when creating a child route with routeContext from the root route with context', () => {
@@ -385,28 +414,37 @@ test('when creating a child route with a loader from the root route', () => {
     },
   })
 
-  expectTypeOf(invoicesRoute.useLoaderData<string>)
+  // eslint-disable-next-line unused-imports/no-unused-vars
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([invoicesRoute]),
+  })
+
+  expectTypeOf(invoicesRoute.useLoaderData<typeof router, string>)
     .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('select')
     .toEqualTypeOf<
-      | {
-          select?: (
-            search: readonly [
-              { readonly id: 'invoice1' },
-              { readonly id: 'invoice2' },
-            ],
-          ) => string
-        }
+      | ((
+          search: readonly [
+            { readonly id: 'invoice1' },
+            { readonly id: 'invoice2' },
+          ],
+        ) => string)
       | undefined
     >()
 
-  expectTypeOf(invoicesRoute.useLoaderData()).toEqualTypeOf<
+  expectTypeOf(invoicesRoute.useLoaderData<typeof router, string>)
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<boolean | undefined>()
+
+  expectTypeOf(invoicesRoute.useLoaderData<typeof router>()).toEqualTypeOf<
     readonly [{ readonly id: 'invoice1' }, { readonly id: 'invoice2' }]
   >()
 })
 
 test('when creating a child route with a loader from the root route with context', () => {
-  const rootRoute = createRootRouteWithContext<{ userId: string }>()()
-
   const invoicesRoute = createRoute({
     path: 'invoices',
     getParentRoute: () => rootRoute,
@@ -427,21 +465,43 @@ test('when creating a child route with a loader from the root route with context
     },
   })
 
-  expectTypeOf(invoicesRoute.useLoaderData<string>)
+  const rootRoute = createRootRouteWithContext<{
+    userId: string
+  }>()()
+
+  const routeTree = rootRoute.addChildren([invoicesRoute])
+
+  // eslint-disable-next-line unused-imports/no-unused-vars
+  const router = createRouter({
+    routeTree,
+    context: { userId: '123' },
+  })
+
+  expectTypeOf(invoicesRoute.useLoaderData<typeof router, string>)
     .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('select')
     .toEqualTypeOf<
-      | {
-          select?: (
-            search: readonly [
-              { readonly id: 'invoice1' },
-              { readonly id: 'invoice2' },
-            ],
-          ) => string
-        }
+      | ((
+          search: readonly [
+            { readonly id: 'invoice1' },
+            { readonly id: 'invoice2' },
+          ],
+        ) => string)
       | undefined
     >()
 
-  expectTypeOf(invoicesRoute.useLoaderData()).toEqualTypeOf<
+  expectTypeOf(invoicesRoute.useLoaderData<typeof router, string>)
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<boolean | undefined>()
+
+  expectTypeOf(invoicesRoute.useLoaderData<typeof router>()).toEqualTypeOf<
+    readonly [{ readonly id: 'invoice1' }, { readonly id: 'invoice2' }]
+  >()
+
+  expectTypeOf(invoicesRoute.useLoaderData<typeof router>()).toEqualTypeOf<
     readonly [{ readonly id: 'invoice1' }, { readonly id: 'invoice2' }]
   >()
 })
@@ -455,15 +515,26 @@ test('when creating a child route with search params from the root route', () =>
     validateSearch: () => ({ page: 0 }),
   })
 
-  expectTypeOf(invoicesRoute.useSearch()).toEqualTypeOf<{
+  // eslint-disable-next-line unused-imports/no-unused-vars
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([invoicesRoute]),
+  })
+
+  expectTypeOf(invoicesRoute.useSearch<typeof router>()).toEqualTypeOf<{
     page: number
   }>()
 
-  expectTypeOf(invoicesRoute.useSearch<number>)
+  expectTypeOf(invoicesRoute.useSearch<typeof router, number>)
     .parameter(0)
-    .toEqualTypeOf<
-      { select?: (search: { page: number }) => number } | undefined
-    >()
+    .exclude<undefined>()
+    .toHaveProperty('select')
+    .toEqualTypeOf<((search: { page: number }) => number) | undefined>()
+
+  expectTypeOf(invoicesRoute.useSearch<typeof router, string>)
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<boolean | undefined>()
 })
 
 test('when creating a child route with optional search params from the root route', () => {
@@ -475,13 +546,28 @@ test('when creating a child route with optional search params from the root rout
     validateSearch: (): { page?: number } => ({ page: 0 }),
   })
 
-  expectTypeOf(invoicesRoute.useSearch()).toEqualTypeOf<{ page?: number }>()
+  // eslint-disable-next-line unused-imports/no-unused-vars
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([invoicesRoute]),
+  })
+
+  expectTypeOf(invoicesRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page?: number
+  }>()
 
-  expectTypeOf(invoicesRoute.useSearch<number>)
+  expectTypeOf(invoicesRoute.useSearch<typeof router, number>)
     .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('select')
     .toEqualTypeOf<
-      { select?: (search: { page?: number }) => number } | undefined
+      ((search: { page?: number | undefined }) => number) | undefined
     >()
+
+  expectTypeOf(invoicesRoute.useSearch<typeof router, string>)
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<boolean | undefined>()
 })
 
 test('when creating a child route with params from the root route', () => {
@@ -492,12 +578,25 @@ test('when creating a child route with params from the root route', () => {
     getParentRoute: () => rootRoute,
   })
 
-  expectTypeOf(invoicesRoute.useParams()).toEqualTypeOf<{ invoiceId: string }>()
-  expectTypeOf(invoicesRoute.useParams<string>)
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([invoicesRoute]),
+  })
+
+  expectTypeOf(invoicesRoute.useParams<typeof router>()).toEqualTypeOf<{
+    invoiceId: string
+  }>()
+
+  expectTypeOf(invoicesRoute.useParams<typeof router, number>)
     .parameter(0)
-    .toEqualTypeOf<
-      { select?: (search: { invoiceId: string }) => string } | undefined
-    >()
+    .exclude<undefined>()
+    .toHaveProperty('select')
+    .toEqualTypeOf<((params: { invoiceId: string }) => number) | undefined>()
+
+  expectTypeOf(invoicesRoute.useParams<typeof router, string>)
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<boolean | undefined>()
 })
 
 test('when creating a child route with a splat param from the root route', () => {
@@ -508,12 +607,26 @@ test('when creating a child route with a splat param from the root route', () =>
     getParentRoute: () => rootRoute,
   })
 
-  expectTypeOf(invoicesRoute.useParams()).toEqualTypeOf<{ _splat?: string }>()
-  expectTypeOf(invoicesRoute.useParams<string>)
+  // eslint-disable-next-line unused-imports/no-unused-vars
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([invoicesRoute]),
+  })
+
+  expectTypeOf(invoicesRoute.useParams<typeof router>()).toEqualTypeOf<{
+    _splat?: string
+  }>()
+
+  expectTypeOf(invoicesRoute.useParams<typeof router, number>)
     .parameter(0)
-    .toEqualTypeOf<
-      { select?: (search: { _splat?: string }) => string } | undefined
-    >()
+    .exclude<undefined>()
+    .toHaveProperty('select')
+    .toEqualTypeOf<((params: { _splat?: string }) => number) | undefined>()
+
+  expectTypeOf(invoicesRoute.useParams<typeof router, string>)
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<boolean | undefined>()
 })
 
 test('when creating a child route with a param and splat param from the root route', () => {
@@ -523,17 +636,29 @@ test('when creating a child route with a param and splat param from the root rou
     path: 'invoices/$invoiceId/$',
     getParentRoute: () => rootRoute,
   })
+  // eslint-disable-next-line unused-imports/no-unused-vars
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([invoicesRoute]),
+  })
 
-  expectTypeOf(invoicesRoute.useParams()).toEqualTypeOf<{
+  expectTypeOf(invoicesRoute.useParams<typeof router>()).toEqualTypeOf<{
     invoiceId: string
     _splat?: string
   }>()
-  expectTypeOf(invoicesRoute.useParams<string>)
+
+  expectTypeOf(invoicesRoute.useParams<typeof router, number>)
     .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('select')
     .toEqualTypeOf<
-      | { select?: (search: { invoiceId: string; _splat?: string }) => string }
-      | undefined
+      ((params: { invoiceId: string; _splat?: string }) => number) | undefined
     >()
+
+  expectTypeOf(invoicesRoute.useParams<typeof router, string>)
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<boolean | undefined>()
 })
 
 test('when creating a child route with params, search and loader from the root route', () => {
@@ -725,19 +850,31 @@ test('when creating a child route with params from a parent with params', () =>
     getParentRoute: () => invoicesRoute,
   })
 
-  expectTypeOf(detailsRoute.useParams()).toEqualTypeOf<{
+  // eslint-disable-next-line unused-imports/no-unused-vars
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([
+      invoicesRoute.addChildren([detailsRoute]),
+    ]),
+  })
+
+  expectTypeOf(detailsRoute.useParams<typeof router>()).toEqualTypeOf<{
     invoiceId: string
     detailId: string
   }>()
 
-  expectTypeOf(detailsRoute.useParams<string>)
+  expectTypeOf(detailsRoute.useParams<typeof router, number>)
     .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('select')
     .toEqualTypeOf<
-      | {
-          select?: (search: { invoiceId: string; detailId: string }) => string
-        }
-      | undefined
+      ((params: { invoiceId: string; detailId: string }) => number) | undefined
     >()
+
+  expectTypeOf(detailsRoute.useParams<typeof router, string>)
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<boolean | undefined>()
 })
 
 test('when creating a child route with search from a parent with search', () => {
@@ -755,22 +892,32 @@ test('when creating a child route with search from a parent with search', () =>
     validateSearch: () => ({ detailPage: 0 }),
   })
 
-  expectTypeOf(detailsRoute.useSearch()).toEqualTypeOf<{
+  // eslint-disable-next-line unused-imports/no-unused-vars
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([
+      invoicesRoute.addChildren([detailsRoute]),
+    ]),
+  })
+
+  expectTypeOf(detailsRoute.useSearch<typeof router>()).toEqualTypeOf<{
     invoicePage: number
     detailPage: number
   }>()
 
-  expectTypeOf(detailsRoute.useSearch<number>)
+  expectTypeOf(detailsRoute.useSearch<typeof router, number>)
     .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('select')
     .toEqualTypeOf<
-      | {
-          select?: (search: {
-            invoicePage: number
-            detailPage: number
-          }) => number
-        }
+      | ((params: { invoicePage: number; detailPage: number }) => number)
       | undefined
     >()
+
+  expectTypeOf(detailsRoute.useSearch<typeof router, number>)
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<boolean | undefined>()
 })
 
 test('when creating a child route with routeContext from a parent with routeContext', () => {
@@ -818,22 +965,30 @@ test('when creating a child route with routeContext from a parent with routeCont
     },
   })
 
-  expectTypeOf(detailsRoute.useRouteContext()).toEqualTypeOf<{
+  // eslint-disable-next-line unused-imports/no-unused-vars
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([
+      invoicesRoute.addChildren([detailsRoute]),
+    ]),
+    context: { userId: '123' },
+  })
+
+  expectTypeOf(detailsRoute.useRouteContext<typeof router>()).toEqualTypeOf<{
     userId: string
     invoiceId: string
     detailId: string
   }>()
 
-  expectTypeOf(detailsRoute.useRouteContext<string>)
+  expectTypeOf(detailsRoute.useRouteContext<typeof router, number>)
     .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('select')
     .toEqualTypeOf<
-      | {
-          select?: (search: {
-            userId: string
-            invoiceId: string
-            detailId: string
-          }) => string
-        }
+      | ((params: {
+          userId: string
+          invoiceId: string
+          detailId: string
+        }) => number)
       | undefined
     >()
 })
@@ -881,22 +1036,30 @@ test('when creating a child route with beforeLoad from a parent with beforeLoad'
     },
   })
 
-  expectTypeOf(detailsRoute.useRouteContext()).toEqualTypeOf<{
+  // eslint-disable-next-line unused-imports/no-unused-vars
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([
+      invoicesRoute.addChildren([detailsRoute]),
+    ]),
+    context: { userId: '123' },
+  })
+
+  expectTypeOf(detailsRoute.useRouteContext<typeof router>()).toEqualTypeOf<{
     userId: string
     invoiceId: string
     detailId: string
   }>()
 
-  expectTypeOf(detailsRoute.useRouteContext<string>)
+  expectTypeOf(detailsRoute.useRouteContext<typeof router, number>)
     .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('select')
     .toEqualTypeOf<
-      | {
-          select?: (search: {
-            userId: string
-            invoiceId: string
-            detailId: string
-          }) => string
-        }
+      | ((params: {
+          userId: string
+          invoiceId: string
+          detailId: string
+        }) => number)
       | undefined
     >()
 })
@@ -1185,7 +1348,11 @@ test('when creating a child route with parseParams and stringify params without
     },
   })
 
-  expectTypeOf(invoicesRoute.useParams()).toEqualTypeOf<{}>()
+  const routeTree = rootRoute.addChildren([invoicesRoute])
+
+  const router = createRouter({ routeTree })
+
+  expectTypeOf(invoicesRoute.useParams<typeof router>()).toEqualTypeOf<{}>()
 })
 
 test('when creating a child route with params.parse and params.stringify without params in path', () => {
@@ -1212,7 +1379,11 @@ test('when creating a child route with params.parse and params.stringify without
     },
   })
 
-  expectTypeOf(invoicesRoute.useParams()).toEqualTypeOf<{}>()
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([invoicesRoute]),
+  })
+
+  expectTypeOf(invoicesRoute.useParams<typeof router>()).toEqualTypeOf<{}>()
 })
 
 test('when creating a child route with parseParams and stringifyParams with params in path', () => {
@@ -1246,7 +1417,13 @@ test('when creating a child route with parseParams and stringifyParams with para
     },
   })
 
-  expectTypeOf(invoiceRoute.useParams()).toEqualTypeOf<{ invoiceId: number }>()
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([invoiceRoute]),
+  })
+
+  expectTypeOf(invoiceRoute.useParams<typeof router>()).toEqualTypeOf<{
+    invoiceId: number
+  }>()
 })
 
 test('when creating a child route with params.parse and params.stringify with params in path', () => {
@@ -1282,7 +1459,13 @@ test('when creating a child route with params.parse and params.stringify with pa
     },
   })
 
-  expectTypeOf(invoiceRoute.useParams()).toEqualTypeOf<{ invoiceId: number }>()
+  const router = createRouter({
+    routeTree: invoicesRoute.addChildren([invoiceRoute]),
+  })
+
+  expectTypeOf(invoiceRoute.useParams<typeof router>()).toEqualTypeOf<{
+    invoiceId: number
+  }>()
 })
 
 test('when creating a child route with parseParams and stringifyParams with merged params from parent', () => {
@@ -1342,7 +1525,13 @@ test('when creating a child route with parseParams and stringifyParams with merg
     },
   })
 
-  expectTypeOf(detailRoute.useParams()).toEqualTypeOf<{
+  const router = createRouter({
+    routeTree: invoicesRoute.addChildren([
+      invoiceRoute.addChildren([detailRoute]),
+    ]),
+  })
+
+  expectTypeOf(detailRoute.useParams<typeof router>()).toEqualTypeOf<{
     detailId: number
     invoiceId: number
   }>()
@@ -1408,7 +1597,13 @@ test('when creating a child route with params.parse and params.stringify with me
     },
   })
 
-  expectTypeOf(detailRoute.useParams()).toEqualTypeOf<{
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([
+      invoicesRoute.addChildren([invoiceRoute.addChildren([detailRoute])]),
+    ]),
+  })
+
+  expectTypeOf(detailRoute.useParams<typeof router>()).toEqualTypeOf<{
     detailId: number
     invoiceId: number
   }>()
@@ -1424,7 +1619,13 @@ test('when routeContext throws', () => {
     },
   })
 
-  expectTypeOf(invoicesRoute.useRouteContext()).toEqualTypeOf<{}>()
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([invoicesRoute]),
+  })
+
+  expectTypeOf(
+    invoicesRoute.useRouteContext<typeof router>(),
+  ).toEqualTypeOf<{}>()
 })
 
 test('when beforeLoad throws', () => {
@@ -1437,7 +1638,13 @@ test('when beforeLoad throws', () => {
     },
   })
 
-  expectTypeOf(invoicesRoute.useRouteContext()).toEqualTypeOf<{}>()
+  const router = createRouter({
+    routeTree: rootRoute.addChildren([invoicesRoute]),
+  })
+
+  expectTypeOf(
+    invoicesRoute.useRouteContext<typeof router>(),
+  ).toEqualTypeOf<{}>()
 })
 
 test('when creating a child route with no explicit search input', () => {
@@ -1450,8 +1657,6 @@ test('when creating a child route with no explicit search input', () => {
     },
   })
 
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page: number }>()
-
   const rootRouteWithContext = createRootRouteWithContext()({
     validateSearch: (input) => {
       expectTypeOf(input).toEqualTypeOf<Record<string, unknown>>()
@@ -1461,10 +1666,6 @@ test('when creating a child route with no explicit search input', () => {
     },
   })
 
-  expectTypeOf(rootRouteWithContext.useSearch()).toEqualTypeOf<{
-    page: number
-  }>()
-
   const indexRoute = createRoute({
     getParentRoute: () => rootRoute,
     path: '/',
@@ -1476,12 +1677,22 @@ test('when creating a child route with no explicit search input', () => {
     },
   })
 
-  expectTypeOf(indexRoute.useSearch()).toEqualTypeOf<{ page: number }>()
-
   const routeTree = rootRoute.addChildren([indexRoute])
 
   const router = createRouter({ routeTree })
 
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: number
+  }>()
+
+  expectTypeOf(indexRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: number
+  }>()
+
+  expectTypeOf(rootRouteWithContext.useSearch<typeof router>()).toEqualTypeOf<{
+    page: number
+  }>()
+
   const navigate = indexRoute.useNavigate()
 
   expectTypeOf(navigate<typeof router, '/', '/'>)
@@ -1511,8 +1722,6 @@ test('when creating a child route with an explicit search input', () => {
     },
   })
 
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page: string }>()
-
   const rootRouteWithContext = createRootRouteWithContext()({
     validateSearch: (input: SearchSchemaInput & { input: string }) => {
       return {
@@ -1521,10 +1730,6 @@ test('when creating a child route with an explicit search input', () => {
     },
   })
 
-  expectTypeOf(rootRouteWithContext.useSearch()).toEqualTypeOf<{
-    page: string
-  }>()
-
   const indexRoute = createRoute({
     getParentRoute: () => rootRoute,
     path: '/',
@@ -1535,12 +1740,22 @@ test('when creating a child route with an explicit search input', () => {
     },
   })
 
-  expectTypeOf(indexRoute.useSearch()).toEqualTypeOf<{ page: string }>()
-
   const routeTree = rootRoute.addChildren([indexRoute])
 
   const router = createRouter({ routeTree })
 
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: string
+  }>()
+
+  expectTypeOf(indexRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: string
+  }>()
+
+  expectTypeOf(rootRouteWithContext.useSearch<typeof router>()).toEqualTypeOf<{
+    page: string
+  }>()
+
   const navigate = indexRoute.useNavigate()
 
   expectTypeOf(navigate<typeof router, '/', '/'>)
diff --git a/packages/react-router/tests/routeApi.test-d.tsx b/packages/react-router/tests/routeApi.test-d.tsx
index c11ec3ee4..972be17b5 100644
--- a/packages/react-router/tests/routeApi.test-d.tsx
+++ b/packages/react-router/tests/routeApi.test-d.tsx
@@ -56,32 +56,34 @@ describe('getRouteApi', () => {
     })
   })
   test('useParams', () => {
-    expectTypeOf(invoiceRouteApi.useParams()).toEqualTypeOf<{
+    expectTypeOf(invoiceRouteApi.useParams<DefaultRouter>()).toEqualTypeOf<{
       invoiceId: string
     }>()
   })
   test('useContext', () => {
-    expectTypeOf(invoiceRouteApi.useRouteContext()).toEqualTypeOf<{
+    expectTypeOf(
+      invoiceRouteApi.useRouteContext<DefaultRouter>(),
+    ).toEqualTypeOf<{
       beforeLoadContext: number
     }>()
   })
   test('useSearch', () => {
-    expectTypeOf(invoiceRouteApi.useSearch()).toEqualTypeOf<{
+    expectTypeOf(invoiceRouteApi.useSearch<DefaultRouter>()).toEqualTypeOf<{
       page: number
     }>()
   })
   test('useLoaderData', () => {
-    expectTypeOf(invoiceRouteApi.useLoaderData()).toEqualTypeOf<{
+    expectTypeOf(invoiceRouteApi.useLoaderData<DefaultRouter>()).toEqualTypeOf<{
       data: number
     }>()
   })
   test('useLoaderDeps', () => {
-    expectTypeOf(invoiceRouteApi.useLoaderDeps()).toEqualTypeOf<{
+    expectTypeOf(invoiceRouteApi.useLoaderDeps<DefaultRouter>()).toEqualTypeOf<{
       dep: number
     }>()
   })
   test('useMatch', () => {
-    expectTypeOf(invoiceRouteApi.useMatch()).toEqualTypeOf<
+    expectTypeOf(invoiceRouteApi.useMatch<DefaultRouter>()).toEqualTypeOf<
       MakeRouteMatch<typeof routeTree, '/invoices/$invoiceId'>
     >()
   })
diff --git a/packages/react-router/tests/router.test-d.tsx b/packages/react-router/tests/router.test-d.tsx
index 9e3471062..f53beb063 100644
--- a/packages/react-router/tests/router.test-d.tsx
+++ b/packages/react-router/tests/router.test-d.tsx
@@ -7,21 +7,22 @@ import {
 } from '../src'
 
 test('when creating a router without context', () => {
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const rootRoute = createRootRoute()
 
   type RouteTree = typeof rootRoute
 
-  expectTypeOf(createRouter<RouteTree, 'never'>)
+  expectTypeOf(createRouter<RouteTree, 'never', boolean>)
     .parameter(0)
     .toHaveProperty('routeTree')
     .toEqualTypeOf<RouteTree | undefined>()
 
-  expectTypeOf(createRouter<RouteTree, 'never'>)
+  expectTypeOf(createRouter<RouteTree, 'never', boolean>)
     .parameter(0)
     .toHaveProperty('context')
     .toEqualTypeOf<{} | undefined>()
 
-  expectTypeOf(createRouter<RouteTree, 'never'>)
+  expectTypeOf(createRouter<RouteTree, 'never', boolean>)
     .parameter(0)
     .not.toMatchTypeOf<{
       context: {}
@@ -97,21 +98,22 @@ test('when building location using router', () => {
 })
 
 test('when creating a router with context', () => {
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const rootRoute = createRootRouteWithContext<{ userId: string }>()()
 
   type RouteTree = typeof rootRoute
 
-  expectTypeOf(createRouter<RouteTree, 'never'>)
+  expectTypeOf(createRouter<RouteTree, 'never', boolean>)
     .parameter(0)
     .toHaveProperty('routeTree')
     .toEqualTypeOf<RouteTree | undefined>()
 
-  expectTypeOf(createRouter<RouteTree, 'never'>)
+  expectTypeOf(createRouter<RouteTree, 'never', boolean>)
     .parameter(0)
     .toHaveProperty('context')
     .toEqualTypeOf<{ userId: string }>()
 
-  expectTypeOf(createRouter<RouteTree, 'never'>)
+  expectTypeOf(createRouter<RouteTree, 'never', boolean>)
     .parameter(0)
     .toMatchTypeOf<{
       context: { userId: string }
@@ -126,21 +128,22 @@ test('when creating a router with context and children', () => {
     path: '/',
   })
 
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const routeTree = rootRoute.addChildren([indexRoute])
 
   type RouteTree = typeof routeTree
 
-  expectTypeOf(createRouter<RouteTree, 'never'>)
+  expectTypeOf(createRouter<RouteTree, 'never', boolean>)
     .parameter(0)
     .toHaveProperty('routeTree')
     .toEqualTypeOf<RouteTree | undefined>()
 
-  expectTypeOf(createRouter<RouteTree, 'never'>)
+  expectTypeOf(createRouter<RouteTree, 'never', boolean>)
     .parameter(0)
     .toHaveProperty('context')
     .toEqualTypeOf<{ userId: string }>()
 
-  expectTypeOf(createRouter<RouteTree, 'never'>)
+  expectTypeOf(createRouter<RouteTree, 'never', boolean>)
     .parameter(0)
     .toMatchTypeOf<{
       context: { userId: string }
diff --git a/packages/react-router/tests/useLoaderData.test-d.tsx b/packages/react-router/tests/useLoaderData.test-d.tsx
index b38d58bb9..6d8a16f84 100644
--- a/packages/react-router/tests/useLoaderData.test-d.tsx
+++ b/packages/react-router/tests/useLoaderData.test-d.tsx
@@ -5,7 +5,6 @@ import {
   createRouter,
   useLoaderData,
 } from '../src'
-import type { MakeRouteMatch } from '../src/Matches'
 
 test('when there is no loaders', () => {
   const rootRoute = createRootRoute()
@@ -30,23 +29,24 @@ test('when there is no loaders', () => {
     indexRoute,
   ])
 
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const defaultRouter = createRouter({
     routeTree,
   })
 
   type DefaultRouter = typeof defaultRouter
 
-  expectTypeOf(useLoaderData<DefaultRouter['routeTree']>)
+  expectTypeOf(useLoaderData<DefaultRouter>)
     .parameter(0)
     .toHaveProperty('from')
     .toEqualTypeOf<'/invoices' | '__root__' | '/invoices/' | '/'>()
 
-  expectTypeOf(useLoaderData<DefaultRouter['routeTree']>)
+  expectTypeOf(useLoaderData<DefaultRouter>)
     .parameter(0)
     .toHaveProperty('strict')
     .toEqualTypeOf<true | undefined>()
 
-  expectTypeOf(useLoaderData<DefaultRouter['routeTree'], '/'>)
+  expectTypeOf(useLoaderData<DefaultRouter, '/'>)
     .parameter(0)
     .toHaveProperty('select')
     .parameter(0)
@@ -54,17 +54,17 @@ test('when there is no loaders', () => {
       loaderData?: {}
     }>()
 
-  expectTypeOf(useLoaderData<DefaultRouter['routeTree'], '/'>)
+  expectTypeOf(useLoaderData<DefaultRouter, '/'>)
     .parameter(0)
     .toHaveProperty('select')
-    .returns.toEqualTypeOf<{}>()
+    .returns.toEqualTypeOf<unknown>()
 
-  expectTypeOf(
-    useLoaderData<DefaultRouter['routeTree'], '/'>,
-  ).returns.toEqualTypeOf<{}>()
+  expectTypeOf(useLoaderData<DefaultRouter, '/'>).returns.toEqualTypeOf<{}>()
 
   expectTypeOf(
-    useLoaderData<DefaultRouter['routeTree'], '/', false>({ strict: false }),
+    useLoaderData<DefaultRouter, '/', false>({
+      strict: false,
+    }),
   ).toEqualTypeOf<{}>
 })
 
@@ -97,6 +97,7 @@ test('when there is one loader', () => {
     indexRoute,
   ])
 
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const defaultRouter = createRouter({
     routeTree,
     context: { userId: 'userId' },
@@ -105,35 +106,29 @@ test('when there is one loader', () => {
   type DefaultRouter = typeof defaultRouter
 
   expectTypeOf(
-    useLoaderData<DefaultRouter['routeTree'], '/invoices'>,
+    useLoaderData<DefaultRouter, '/invoices'>,
   ).returns.toEqualTypeOf<{ data: Array<string> }>()
 
   expectTypeOf(
-    useLoaderData<DefaultRouter['routeTree'], '/invoices'>,
+    useLoaderData<DefaultRouter, '/invoices'>,
   ).returns.toEqualTypeOf<{ data: Array<string> }>()
 
-  expectTypeOf(useLoaderData<DefaultRouter['routeTree'], '/invoices'>)
+  expectTypeOf(useLoaderData<DefaultRouter, '/invoices'>)
     .parameter(0)
     .toHaveProperty('select')
-    .returns.toEqualTypeOf<{ data: Array<string> }>()
+    .returns.toEqualTypeOf<unknown>()
 
   expectTypeOf(
-    useLoaderData<DefaultRouter['routeTree'], '/invoices', false>,
+    useLoaderData<DefaultRouter, '/invoices', false>,
   ).returns.toEqualTypeOf<{ data?: Array<string> }>()
 
-  expectTypeOf(useLoaderData<DefaultRouter['routeTree'], '/invoices', false>)
+  expectTypeOf(useLoaderData<DefaultRouter, '/invoices', false>)
     .parameter(0)
     .toHaveProperty('select')
-    .returns.toEqualTypeOf<{ data?: Array<string> }>()
+    .returns.toEqualTypeOf<unknown>()
 
   expectTypeOf(
-    useLoaderData<
-      DefaultRouter['routeTree'],
-      '/invoices',
-      false,
-      MakeRouteMatch<DefaultRouter['routeTree'], '/invoices', false>,
-      number
-    >,
+    useLoaderData<DefaultRouter, '/invoices', false, number>,
   ).returns.toEqualTypeOf<number>()
 })
 
@@ -166,6 +161,7 @@ test('when there is one loader that is async', () => {
     indexRoute,
   ])
 
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const defaultRouter = createRouter({
     routeTree,
     context: { userId: 'userId' },
@@ -174,35 +170,29 @@ test('when there is one loader that is async', () => {
   type DefaultRouter = typeof defaultRouter
 
   expectTypeOf(
-    useLoaderData<DefaultRouter['routeTree'], '/invoices'>,
+    useLoaderData<DefaultRouter, '/invoices'>,
   ).returns.toEqualTypeOf<{ data: Array<string> }>()
 
   expectTypeOf(
-    useLoaderData<DefaultRouter['routeTree'], '/invoices'>,
+    useLoaderData<DefaultRouter, '/invoices'>,
   ).returns.toEqualTypeOf<{ data: Array<string> }>()
 
-  expectTypeOf(useLoaderData<DefaultRouter['routeTree'], '/invoices'>)
+  expectTypeOf(useLoaderData<DefaultRouter, '/invoices'>)
     .parameter(0)
     .toHaveProperty('select')
-    .returns.toEqualTypeOf<{ data: Array<string> }>()
+    .returns.toEqualTypeOf<unknown>()
 
   expectTypeOf(
-    useLoaderData<DefaultRouter['routeTree'], '/invoices', false>,
+    useLoaderData<DefaultRouter, '/invoices', false>,
   ).returns.toEqualTypeOf<{ data?: Array<string> }>()
 
-  expectTypeOf(useLoaderData<DefaultRouter['routeTree'], '/invoices', false>)
+  expectTypeOf(useLoaderData<DefaultRouter, '/invoices', false>)
     .parameter(0)
     .toHaveProperty('select')
-    .returns.toEqualTypeOf<{ data?: Array<string> }>()
+    .returns.toEqualTypeOf<unknown>()
 
   expectTypeOf(
-    useLoaderData<
-      DefaultRouter['routeTree'],
-      '/invoices',
-      false,
-      MakeRouteMatch<DefaultRouter['routeTree'], '/invoices', false>,
-      number
-    >,
+    useLoaderData<DefaultRouter, '/invoices', false, number>,
   ).returns.toEqualTypeOf<number>()
 })
 
@@ -237,31 +227,28 @@ test('when there are multiple loaders', () => {
     indexRoute,
   ])
 
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const defaultRouter = createRouter({
     routeTree,
   })
 
   type DefaultRouter = typeof defaultRouter
 
-  expectTypeOf(
-    useLoaderData<DefaultRouter['routeTree'], '/'>,
-  ).returns.toEqualTypeOf<{}>()
+  expectTypeOf(useLoaderData<DefaultRouter, '/'>).returns.toEqualTypeOf<{}>()
 
   expectTypeOf(
-    useLoaderData<DefaultRouter['routeTree'], '/invoices'>,
+    useLoaderData<DefaultRouter, '/invoices'>,
   ).returns.toEqualTypeOf<{
     readonly data: readonly ['invoice1', 'invoice2']
   }>()
 
-  expectTypeOf(useLoaderData<DefaultRouter['routeTree'], '/invoices'>)
+  expectTypeOf(useLoaderData<DefaultRouter, '/invoices'>)
     .parameter(0)
     .toHaveProperty('select')
-    .returns.toEqualTypeOf<{
-      readonly data: readonly ['invoice1', 'invoice2']
-    }>()
+    .returns.toEqualTypeOf<unknown>()
 
   expectTypeOf(
-    useLoaderData<DefaultRouter['routeTree'], '/invoices', false>,
+    useLoaderData<DefaultRouter, '/invoices', false>,
   ).returns.toEqualTypeOf<{
     data?:
       | readonly ['invoice1', 'invoice2']
@@ -269,15 +256,91 @@ test('when there are multiple loaders', () => {
       | undefined
   }>()
 
-  expectTypeOf(useLoaderData<DefaultRouter['routeTree'], '/invoices', false>)
+  expectTypeOf(useLoaderData<DefaultRouter, '/invoices', false>)
     .parameter(0)
     .toHaveProperty('select')
-    .returns.toEqualTypeOf<{
+    .returns.toEqualTypeOf<unknown>()
+
+  expectTypeOf(useLoaderData<DefaultRouter, '/invoices', false>)
+    .parameter(0)
+    .toHaveProperty('select')
+    .parameter(0)
+    .toEqualTypeOf<{
       data?:
         | readonly ['invoice1', 'invoice2']
         | readonly ['post1', 'post2']
         | undefined
     }>()
+
+  expectTypeOf(
+    useLoaderData<DefaultRouter, '/invoices', true, { func: () => void }>,
+  )
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('select')
+    .exclude<undefined>()
+    .returns.toEqualTypeOf<{ func: () => void }>()
+
+  expectTypeOf(
+    useLoaderData<DefaultRouter, '/invoices', true, { func: () => void }>,
+  )
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<false | undefined>()
+
+  expectTypeOf(
+    useLoaderData<DefaultRouter, '/invoices', true, { func: () => void }, true>,
+  )
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('select')
+    .exclude<undefined>()
+    .returns.toEqualTypeOf<{ func: 'Function is not serializable' }>()
+
+  expectTypeOf(
+    useLoaderData<DefaultRouter, '/invoices', true, { func: () => void }, true>,
+  )
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<false | undefined>()
+
+  const routerWithStructuralSharing = createRouter({
+    routeTree,
+    defaultStructuralSharing: true,
+  })
+
+  expectTypeOf(
+    useLoaderData<
+      typeof routerWithStructuralSharing,
+      '/invoices',
+      true,
+      { func: () => void },
+      true
+    >,
+  )
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('select')
+    .exclude<undefined>()
+    .returns.toEqualTypeOf<{
+      func: 'Function is not serializable'
+    }>()
+
+  expectTypeOf(
+    useLoaderData<
+      typeof routerWithStructuralSharing,
+      '/invoices',
+      true,
+      { func: () => void },
+      true
+    >,
+  )
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<false>()
 })
 
 test('when there are multiple loaders of objects and primtives', () => {
@@ -312,27 +375,32 @@ test('when there are multiple loaders of objects and primtives', () => {
     postsRoute,
   ])
 
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const defaultRouter = createRouter({
     routeTree,
   })
 
   type DefaultRouter = typeof defaultRouter
 
-  expectTypeOf(
-    useLoaderData<DefaultRouter['routeTree'], '/'>,
-  ).returns.toEqualTypeOf<{}>()
+  expectTypeOf(useLoaderData<DefaultRouter, '/'>).returns.toEqualTypeOf<{}>()
 
-  expectTypeOf(
-    useLoaderData<DefaultRouter['routeTree'], '/invoices'>,
-  ).returns.toEqualTypeOf<readonly ['invoice1', 'invoice2']>()
+  expectTypeOf(useLoaderData<DefaultRouter, '/invoices'>).returns.toEqualTypeOf<
+    readonly ['invoice1', 'invoice2']
+  >()
+
+  expectTypeOf(useLoaderData<DefaultRouter, '/invoices'>)
+    .parameter(0)
+    .toHaveProperty('select')
+    .returns.toEqualTypeOf<unknown>()
 
-  expectTypeOf(useLoaderData<DefaultRouter['routeTree'], '/invoices'>)
+  expectTypeOf(useLoaderData<DefaultRouter, '/invoices'>)
     .parameter(0)
     .toHaveProperty('select')
-    .returns.toEqualTypeOf<readonly ['invoice1', 'invoice2']>()
+    .parameter(0)
+    .toEqualTypeOf<readonly ['invoice1', 'invoice2']>()
 
   expectTypeOf(
-    useLoaderData<DefaultRouter['routeTree'], '/invoices', false>,
+    useLoaderData<DefaultRouter, '/invoices', false>,
   ).returns.toEqualTypeOf<
     | readonly ['invoice1', 'invoice2']
     | readonly ['post1', 'post2']
@@ -345,10 +413,11 @@ test('when there are multiple loaders of objects and primtives', () => {
       }
   >()
 
-  expectTypeOf(useLoaderData<DefaultRouter['routeTree'], '/invoices', false>)
+  expectTypeOf(useLoaderData<DefaultRouter, '/invoices', false>)
     .parameter(0)
     .toHaveProperty('select')
-    .returns.toEqualTypeOf<
+    .parameter(0)
+    .toEqualTypeOf<
       | readonly ['invoice1', 'invoice2']
       | readonly ['post1', 'post2']
       | {
diff --git a/packages/react-router/tests/useLocation.test-d.tsx b/packages/react-router/tests/useLocation.test-d.tsx
index c09e8025d..5989a492c 100644
--- a/packages/react-router/tests/useLocation.test-d.tsx
+++ b/packages/react-router/tests/useLocation.test-d.tsx
@@ -1,6 +1,6 @@
 import { expectTypeOf, test } from 'vitest'
 import { createRootRoute, createRoute, createRouter, useLocation } from '../src'
-import type { ParsedLocation, RouterState } from '../src'
+import type { ParsedLocation } from '../src'
 
 const rootRoute = createRootRoute()
 
@@ -30,11 +30,7 @@ test('should have the types for a ParsedLocation in useLocation', () => {
 })
 
 test('should have the type of string for selecting the pathname in useLocation', () => {
-  const pathname = useLocation<
-    DefaultRouter,
-    RouterState<DefaultRouter['routeTree']>['location'],
-    RouterState<DefaultRouter['routeTree']>['location']['pathname']
-  >({
+  const pathname = useLocation<DefaultRouter, string>({
     select: (state) => state.pathname,
   })
 
diff --git a/packages/react-router/tests/useMatch.test-d.tsx b/packages/react-router/tests/useMatch.test-d.tsx
index d877ca78f..7ca903ce8 100644
--- a/packages/react-router/tests/useMatch.test-d.tsx
+++ b/packages/react-router/tests/useMatch.test-d.tsx
@@ -16,6 +16,7 @@ const invoicesRoute = createRoute({
 
 const routeTree = rootRoute.addChildren([invoicesRoute, indexRoute])
 
+// eslint-disable-next-line unused-imports/no-unused-vars
 const defaultRouter = createRouter({ routeTree })
 
 type DefaultRouter = typeof defaultRouter
@@ -27,12 +28,11 @@ describe('useMatch', () => {
   test('return type is `RouteMatch` when shouldThrow = true', () => {
     const shouldThrow = true
     const match = useMatch<
-      DefaultRouter['routeTree'],
+      DefaultRouter,
       typeof from,
-      true,
-      TRouteMatch,
-      TRouteMatch,
-      typeof shouldThrow
+      true, // TStrict
+      typeof shouldThrow,
+      TRouteMatch
     >({ from, shouldThrow })
 
     expectTypeOf(match).toEqualTypeOf<TRouteMatch>()
@@ -41,12 +41,11 @@ describe('useMatch', () => {
   test('return type is `RouteMatch | undefined` when shouldThrow = false', () => {
     const shouldThrow = false
     const match = useMatch<
-      DefaultRouter['routeTree'],
+      DefaultRouter,
       typeof from,
-      true,
-      TRouteMatch,
-      TRouteMatch,
-      typeof shouldThrow
+      true, // TStrict
+      typeof shouldThrow,
+      TRouteMatch
     >({ from, shouldThrow })
 
     expectTypeOf(match).toEqualTypeOf<TRouteMatch | undefined>()
diff --git a/packages/react-router/tests/useParams.test-d.tsx b/packages/react-router/tests/useParams.test-d.tsx
index 4be82a8f6..4b4f13162 100644
--- a/packages/react-router/tests/useParams.test-d.tsx
+++ b/packages/react-router/tests/useParams.test-d.tsx
@@ -1,6 +1,5 @@
 import { expectTypeOf, test } from 'vitest'
 import { createRootRoute, createRoute, createRouter, useParams } from '../src'
-import type { FullSearchSchema } from '../src'
 
 test('when there are no params', () => {
   const rootRoute = createRootRoute()
@@ -25,39 +24,40 @@ test('when there are no params', () => {
     indexRoute,
   ])
 
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const defaultRouter = createRouter({
     routeTree,
   })
 
   type DefaultRouter = typeof defaultRouter
 
-  expectTypeOf(useParams<DefaultRouter['routeTree']>)
+  expectTypeOf(useParams<DefaultRouter>)
     .parameter(0)
     .toHaveProperty('from')
     .toEqualTypeOf<'/invoices' | '__root__' | '/invoices/' | '/'>()
 
-  expectTypeOf(useParams<DefaultRouter['routeTree']>)
+  expectTypeOf(useParams<DefaultRouter>)
     .parameter(0)
     .toHaveProperty('strict')
     .toEqualTypeOf<true | undefined>()
 
-  expectTypeOf(useParams<DefaultRouter['routeTree'], '/'>)
+  expectTypeOf(useParams<DefaultRouter, '/'>)
     .parameter(0)
     .toHaveProperty('select')
     .parameter(0)
     .toEqualTypeOf<{}>()
 
-  expectTypeOf(useParams<DefaultRouter['routeTree'], '/'>)
+  expectTypeOf(useParams<DefaultRouter, '/'>)
     .parameter(0)
     .toHaveProperty('select')
-    .returns.toEqualTypeOf<{}>()
+    .returns.toEqualTypeOf<unknown>()
 
-  expectTypeOf(
-    useParams<DefaultRouter['routeTree'], '/'>,
-  ).returns.toEqualTypeOf<{}>()
+  expectTypeOf(useParams<DefaultRouter, '/'>).returns.toEqualTypeOf<{}>()
 
   expectTypeOf(
-    useParams<DefaultRouter['routeTree'], '/', false>({ strict: false }),
+    useParams<DefaultRouter, '/', false>({
+      strict: false,
+    }),
   ).toEqualTypeOf<{}>()
 })
 
@@ -89,46 +89,35 @@ test('when there is one param', () => {
     indexRoute,
   ])
 
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const defaultRouter = createRouter({
     routeTree,
   })
 
   type DefaultRouter = typeof defaultRouter
 
-  expectTypeOf(
-    useParams<DefaultRouter['routeTree'], '/'>,
-  ).returns.toEqualTypeOf<{}>()
+  expectTypeOf(useParams<DefaultRouter, '/'>).returns.toEqualTypeOf<{}>()
 
   expectTypeOf(
-    useParams<DefaultRouter['routeTree'], '/invoices/$invoiceId'>,
+    useParams<DefaultRouter, '/invoices/$invoiceId'>,
   ).returns.toEqualTypeOf<{ invoiceId: string }>()
 
-  expectTypeOf(useParams<DefaultRouter['routeTree'], '/invoices/$invoiceId'>)
+  expectTypeOf(useParams<DefaultRouter, '/invoices/$invoiceId'>)
     .parameter(0)
     .toHaveProperty('select')
-    .toEqualTypeOf<
-      ((search: { invoiceId: string }) => { invoiceId: string }) | undefined
-    >()
+    .toEqualTypeOf<((search: { invoiceId: string }) => unknown) | undefined>()
 
   expectTypeOf(
-    useParams<DefaultRouter['routeTree'], '/invoices', false>,
+    useParams<DefaultRouter, '/invoices', false>,
   ).returns.toEqualTypeOf<{ invoiceId?: string }>()
 
-  expectTypeOf(useParams<DefaultRouter['routeTree'], '/invoices', false>)
+  expectTypeOf(useParams<DefaultRouter, '/invoices', false>)
     .parameter(0)
     .toHaveProperty('select')
-    .toEqualTypeOf<
-      ((search: { invoiceId?: string }) => { invoiceId?: string }) | undefined
-    >()
+    .toEqualTypeOf<((search: { invoiceId?: string }) => unknown) | undefined>()
 
   expectTypeOf(
-    useParams<
-      DefaultRouter['routeTree'],
-      '/invoices',
-      false,
-      FullSearchSchema<DefaultRouter['routeTree']>,
-      number
-    >,
+    useParams<DefaultRouter, '/invoices', false, number>,
   ).returns.toEqualTypeOf<number>()
 })
 
@@ -174,39 +163,112 @@ test('when there are multiple params', () => {
     indexRoute,
   ])
 
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const defaultRouter = createRouter({
     routeTree,
   })
 
   type DefaultRouter = typeof defaultRouter
 
-  expectTypeOf(
-    useParams<DefaultRouter['routeTree'], '/'>,
-  ).returns.toEqualTypeOf<{}>()
+  expectTypeOf(useParams<DefaultRouter, '/'>).returns.toEqualTypeOf<{}>()
 
   expectTypeOf(
-    useParams<DefaultRouter['routeTree'], '/invoices/$invoiceId'>,
+    useParams<DefaultRouter, '/invoices/$invoiceId'>,
   ).returns.toEqualTypeOf<{ invoiceId: string }>()
 
-  expectTypeOf(useParams<DefaultRouter['routeTree'], '/invoices/$invoiceId'>)
+  expectTypeOf(useParams<DefaultRouter, '/invoices/$invoiceId'>)
+    .parameter(0)
+    .toHaveProperty('select')
+    .toEqualTypeOf<((search: { invoiceId: string }) => unknown) | undefined>()
+
+  expectTypeOf(
+    useParams<DefaultRouter, '/invoices', false>,
+  ).returns.toEqualTypeOf<{ invoiceId?: string; postId?: string }>()
+
+  expectTypeOf(useParams<DefaultRouter, '/invoices', false>)
     .parameter(0)
     .toHaveProperty('select')
     .toEqualTypeOf<
-      ((search: { invoiceId: string }) => { invoiceId: string }) | undefined
+      ((search: { invoiceId?: string; postId?: string }) => unknown) | undefined
     >()
 
   expectTypeOf(
-    useParams<DefaultRouter['routeTree'], '/invoices', false>,
-  ).returns.toEqualTypeOf<{ invoiceId?: string; postId?: string }>()
+    useParams<DefaultRouter, '/invoices', true, { func: () => void }>,
+  )
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('select')
+    .toEqualTypeOf<
+      | ((search: {}) => {
+          func: () => void
+        })
+      | undefined
+    >()
+
+  expectTypeOf(
+    useParams<DefaultRouter, '/invoices', true, { func: () => void }>,
+  )
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<false | undefined>()
 
-  expectTypeOf(useParams<DefaultRouter['routeTree'], '/invoices', false>)
+  expectTypeOf(
+    useParams<DefaultRouter, '/invoices', true, { func: () => void }, true>,
+  )
     .parameter(0)
+    .exclude<undefined>()
     .toHaveProperty('select')
     .toEqualTypeOf<
-      | ((search: { invoiceId?: string; postId?: string }) => {
-          invoiceId?: string
-          postId?: string
+      | ((search: {}) => {
+          func: 'Function is not serializable'
         })
       | undefined
     >()
+
+  expectTypeOf(
+    useParams<DefaultRouter, '/invoices', true, { func: () => void }, true>,
+  )
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<false | undefined>()
+
+  const routerWithStructuralSharing = createRouter({
+    routeTree,
+    defaultStructuralSharing: true,
+  })
+
+  expectTypeOf(
+    useParams<
+      typeof routerWithStructuralSharing,
+      '/invoices',
+      true,
+      { func: () => void },
+      true
+    >,
+  )
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('select')
+    .toEqualTypeOf<
+      | ((search: {}) => {
+          func: 'Function is not serializable'
+        })
+      | undefined
+    >()
+
+  expectTypeOf(
+    useParams<
+      typeof routerWithStructuralSharing,
+      '/invoices',
+      true,
+      { func: () => void },
+      true
+    >,
+  )
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<false>()
 })
diff --git a/packages/react-router/tests/useRouteContext.test-d.tsx b/packages/react-router/tests/useRouteContext.test-d.tsx
index e94a58866..7d0a14ef4 100644
--- a/packages/react-router/tests/useRouteContext.test-d.tsx
+++ b/packages/react-router/tests/useRouteContext.test-d.tsx
@@ -6,7 +6,6 @@ import {
   createRouter,
   useRouteContext,
 } from '../src'
-import type { FullSearchSchema } from '../src'
 
 test('when there is no context', () => {
   const rootRoute = createRootRoute()
@@ -31,39 +30,40 @@ test('when there is no context', () => {
     indexRoute,
   ])
 
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const defaultRouter = createRouter({
     routeTree,
   })
 
   type DefaultRouter = typeof defaultRouter
 
-  expectTypeOf(useRouteContext<DefaultRouter['routeTree']>)
+  expectTypeOf(useRouteContext<DefaultRouter>)
     .parameter(0)
     .toHaveProperty('from')
     .toEqualTypeOf<'/invoices' | '__root__' | '/invoices/' | '/'>()
 
-  expectTypeOf(useRouteContext<DefaultRouter['routeTree']>)
+  expectTypeOf(useRouteContext<DefaultRouter>)
     .parameter(0)
     .toHaveProperty('strict')
     .toEqualTypeOf<true | undefined>()
 
-  expectTypeOf(useRouteContext<DefaultRouter['routeTree'], '/'>)
+  expectTypeOf(useRouteContext<DefaultRouter, '/'>)
     .parameter(0)
     .toHaveProperty('select')
     .parameter(0)
     .toEqualTypeOf<{}>()
 
-  expectTypeOf(useRouteContext<DefaultRouter['routeTree'], '/'>)
+  expectTypeOf(useRouteContext<DefaultRouter, '/'>)
     .parameter(0)
     .toHaveProperty('select')
-    .returns.toEqualTypeOf<{}>()
+    .returns.toEqualTypeOf<unknown>()
 
-  expectTypeOf(
-    useRouteContext<DefaultRouter['routeTree'], '/'>,
-  ).returns.toEqualTypeOf<{}>()
+  expectTypeOf(useRouteContext<DefaultRouter, '/'>).returns.toEqualTypeOf<{}>()
 
   expectTypeOf(
-    useRouteContext<DefaultRouter['routeTree'], '/', false>({ strict: false }),
+    useRouteContext<DefaultRouter, '/', false>({
+      strict: false,
+    }),
   ).toEqualTypeOf<{}>()
 })
 
@@ -99,6 +99,7 @@ test('when there is the root context', () => {
     indexRoute,
   ])
 
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const defaultRouter = createRouter({
     routeTree,
     context: { userId: 'userId' },
@@ -106,42 +107,30 @@ test('when there is the root context', () => {
 
   type DefaultRouter = typeof defaultRouter
 
-  expectTypeOf(
-    useRouteContext<DefaultRouter['routeTree'], '/'>,
-  ).returns.toEqualTypeOf<{ userId: string }>()
+  expectTypeOf(useRouteContext<DefaultRouter, '/'>).returns.toEqualTypeOf<{
+    userId: string
+  }>()
 
   expectTypeOf(
-    useRouteContext<DefaultRouter['routeTree'], '/invoices/$invoiceId'>,
+    useRouteContext<DefaultRouter, '/invoices/$invoiceId'>,
   ).returns.toEqualTypeOf<{ userId: string }>()
 
-  expectTypeOf(
-    useRouteContext<DefaultRouter['routeTree'], '/invoices/$invoiceId'>,
-  )
+  expectTypeOf(useRouteContext<DefaultRouter, '/invoices/$invoiceId'>)
     .parameter(0)
     .toHaveProperty('select')
-    .toEqualTypeOf<
-      ((search: { userId: string }) => { userId: string }) | undefined
-    >()
+    .toEqualTypeOf<((search: { userId: string }) => unknown) | undefined>()
 
   expectTypeOf(
-    useRouteContext<DefaultRouter['routeTree'], '/invoices', false>,
+    useRouteContext<DefaultRouter, '/invoices', false>,
   ).returns.toEqualTypeOf<{ userId?: string }>()
 
-  expectTypeOf(useRouteContext<DefaultRouter['routeTree'], '/invoices', false>)
+  expectTypeOf(useRouteContext<DefaultRouter, '/invoices', false>)
     .parameter(0)
     .toHaveProperty('select')
-    .toEqualTypeOf<
-      ((search: { userId?: string }) => { userId?: string }) | undefined
-    >()
+    .toEqualTypeOf<((search: { userId?: string }) => unknown) | undefined>()
 
   expectTypeOf(
-    useRouteContext<
-      DefaultRouter['routeTree'],
-      '/invoices',
-      false,
-      FullSearchSchema<DefaultRouter['routeTree']>,
-      number
-    >,
+    useRouteContext<DefaultRouter, '/invoices', false, number>,
   ).returns.toEqualTypeOf<number>()
 })
 
@@ -192,6 +181,7 @@ test('when there are multiple contexts', () => {
     indexRoute,
   ])
 
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const defaultRouter = createRouter({
     routeTree,
     context: { userId: 'userId' },
@@ -199,36 +189,28 @@ test('when there are multiple contexts', () => {
 
   type DefaultRouter = typeof defaultRouter
 
-  expectTypeOf(
-    useRouteContext<DefaultRouter['routeTree'], '/'>,
-  ).returns.toEqualTypeOf<{ userId: string }>()
+  expectTypeOf(useRouteContext<DefaultRouter, '/'>).returns.toEqualTypeOf<{
+    userId: string
+  }>()
 
   expectTypeOf(
-    useRouteContext<DefaultRouter['routeTree'], '/invoices/$invoiceId'>,
+    useRouteContext<DefaultRouter, '/invoices/$invoiceId'>,
   ).returns.toEqualTypeOf<{ userId: string }>()
 
-  expectTypeOf(
-    useRouteContext<DefaultRouter['routeTree'], '/invoices/$invoiceId'>,
-  )
+  expectTypeOf(useRouteContext<DefaultRouter, '/invoices/$invoiceId'>)
     .parameter(0)
     .toHaveProperty('select')
-    .toEqualTypeOf<
-      ((search: { userId: string }) => { userId: string }) | undefined
-    >()
+    .toEqualTypeOf<((search: { userId: string }) => unknown) | undefined>()
 
   expectTypeOf(
-    useRouteContext<DefaultRouter['routeTree'], '/invoices', false>,
+    useRouteContext<DefaultRouter, '/invoices', false>,
   ).returns.toEqualTypeOf<{ userId?: string; username?: string }>()
 
-  expectTypeOf(useRouteContext<DefaultRouter['routeTree'], '/invoices', false>)
+  expectTypeOf(useRouteContext<DefaultRouter, '/invoices', false>)
     .parameter(0)
     .toHaveProperty('select')
     .toEqualTypeOf<
-      | ((search: { userId?: string; username?: string }) => {
-          userId?: string
-          username?: string
-        })
-      | undefined
+      ((search: { userId?: string; username?: string }) => unknown) | undefined
     >()
 })
 
@@ -281,6 +263,7 @@ test('when there are overlapping contexts', () => {
     indexRoute,
   ])
 
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const defaultRouter = createRouter({
     routeTree,
     context: { userId: 'userId' },
@@ -288,47 +271,43 @@ test('when there are overlapping contexts', () => {
 
   type DefaultRouter = typeof defaultRouter
 
-  expectTypeOf(useRouteContext<DefaultRouter['routeTree'], '/'>).returns
-    .toEqualTypeOf<{ userId: string }>
+  expectTypeOf(useRouteContext<DefaultRouter, '/'>).returns.toEqualTypeOf<{
+    userId: string
+  }>
 
   expectTypeOf(
-    useRouteContext<DefaultRouter['routeTree'], '/invoices/$invoiceId'>,
+    useRouteContext<DefaultRouter, '/invoices/$invoiceId'>,
   ).returns.toEqualTypeOf<{
     userId: string
     readonly username: 'username2'
   }>()
 
-  expectTypeOf(
-    useRouteContext<DefaultRouter['routeTree'], '/invoices/$invoiceId'>,
-  )
+  expectTypeOf(useRouteContext<DefaultRouter, '/invoices/$invoiceId'>)
     .parameter(0)
     .toHaveProperty('select')
     .toEqualTypeOf<
-      | ((search: { userId: string; readonly username: 'username2' }) => {
+      | ((search: {
           userId: string
           readonly username: 'username2'
-        })
+        }) => unknown)
       | undefined
     >()
 
   expectTypeOf(
-    useRouteContext<DefaultRouter['routeTree'], '/invoices', false>,
+    useRouteContext<DefaultRouter, '/invoices', false>,
   ).returns.toEqualTypeOf<{
     userId?: string
     username?: 'username1' | 'username2'
   }>()
 
-  expectTypeOf(useRouteContext<DefaultRouter['routeTree'], '/invoices', false>)
+  expectTypeOf(useRouteContext<DefaultRouter, '/invoices', false>)
     .parameter(0)
     .toHaveProperty('select')
     .toEqualTypeOf<
       | ((search: {
           userId?: string
           username?: 'username2' | 'username1'
-        }) => {
-          userId?: string
-          username?: 'username2' | 'username1'
-        })
+        }) => unknown)
       | undefined
     >()
 })
diff --git a/packages/react-router/tests/useRouterState.test-d.tsx b/packages/react-router/tests/useRouterState.test-d.tsx
index 39f135a3e..c91a8005a 100644
--- a/packages/react-router/tests/useRouterState.test-d.tsx
+++ b/packages/react-router/tests/useRouterState.test-d.tsx
@@ -5,6 +5,7 @@ import {
   createRouter,
   useRouterState,
 } from '../src'
+import type { RouterState } from '../src'
 
 const rootRoute = createRootRoute({
   validateSearch: () => ({
@@ -50,4 +51,64 @@ test('can select router state', () => {
     .toMatchTypeOf<{
       search: { page?: number | undefined }
     }>()
+
+  expectTypeOf(useRouterState<DefaultRouter, { func: () => void }>)
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('select')
+    .toEqualTypeOf<
+      | ((search: RouterState<DefaultRouter['routeTree']>) => {
+          func: () => void
+        })
+      | undefined
+    >()
+
+  expectTypeOf(useRouterState<DefaultRouter, { func: () => void }>)
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<false | undefined>()
+
+  expectTypeOf(useRouterState<DefaultRouter, { func: () => void }, true>)
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('select')
+    .toEqualTypeOf<
+      | ((search: RouterState<DefaultRouter['routeTree']>) => {
+          func: 'Function is not serializable'
+        })
+      | undefined
+    >()
+
+  expectTypeOf(useRouterState<DefaultRouter, { func: () => void }, true>)
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<false | undefined>()
+
+  const routerWithStructuralSharing = createRouter({
+    routeTree,
+    defaultStructuralSharing: true,
+  })
+
+  expectTypeOf(
+    useRouterState<typeof routerWithStructuralSharing, { func: () => void }>,
+  )
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('select')
+    .toEqualTypeOf<
+      | ((search: RouterState<DefaultRouter['routeTree']>) => {
+          func: 'Function is not serializable'
+        })
+      | undefined
+    >()
+
+  expectTypeOf(
+    useRouterState<typeof routerWithStructuralSharing, { func: () => void }>,
+  )
+    .parameter(0)
+    .exclude<undefined>()
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<false>()
 })
diff --git a/packages/react-router/tests/useSearch.test-d.tsx b/packages/react-router/tests/useSearch.test-d.tsx
index 0828614b3..5a8ac9337 100644
--- a/packages/react-router/tests/useSearch.test-d.tsx
+++ b/packages/react-router/tests/useSearch.test-d.tsx
@@ -1,6 +1,6 @@
 import { expectTypeOf, test } from 'vitest'
 import { createRootRoute, createRoute, createRouter, useSearch } from '../src'
-import type { FullSearchSchema, SearchSchemaInput } from '../src'
+import type { SearchSchemaInput } from '../src'
 
 test('when there are no search params', () => {
   const rootRoute = createRootRoute()
@@ -30,41 +30,42 @@ test('when there are no search params', () => {
     indexRoute,
   ])
 
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const defaultRouter = createRouter({
     routeTree,
   })
 
   type DefaultRouter = typeof defaultRouter
 
-  expectTypeOf(useSearch<DefaultRouter['routeTree']>)
+  expectTypeOf(useSearch<DefaultRouter>)
     .parameter(0)
     .toHaveProperty('from')
     .toEqualTypeOf<
       '/invoices' | '__root__' | '/invoices/$invoiceId' | '/invoices/' | '/'
     >()
 
-  expectTypeOf(useSearch<DefaultRouter['routeTree']>)
+  expectTypeOf(useSearch<DefaultRouter>)
     .parameter(0)
     .toHaveProperty('strict')
     .toEqualTypeOf<true | undefined>()
 
-  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/'>)
+  expectTypeOf(useSearch<DefaultRouter, '/'>)
     .parameter(0)
     .toHaveProperty('select')
     .parameter(0)
     .toEqualTypeOf<{}>()
 
-  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/'>)
+  expectTypeOf(useSearch<DefaultRouter, '/'>)
     .parameter(0)
     .toHaveProperty('select')
-    .returns.toEqualTypeOf<{}>()
+    .returns.toEqualTypeOf<unknown>()
 
-  expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/'>,
-  ).returns.toEqualTypeOf<{}>()
+  expectTypeOf(useSearch<DefaultRouter, '/'>).returns.toEqualTypeOf<{}>()
 
   expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/', false>({ strict: false }),
+    useSearch<DefaultRouter, '/', false>({
+      strict: false,
+    }),
   ).toEqualTypeOf<{}>()
 })
 
@@ -97,47 +98,152 @@ test('when there is one search params', () => {
     indexRoute,
   ])
 
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const defaultRouter = createRouter({
     routeTree,
   })
 
   type DefaultRouter = typeof defaultRouter
 
+  expectTypeOf(useSearch<DefaultRouter, '/'>).returns.toEqualTypeOf<{}>()
+
+  expectTypeOf(useSearch<DefaultRouter, '/invoices'>).returns.toEqualTypeOf<{
+    page: number
+  }>()
+
+  expectTypeOf(useSearch<DefaultRouter, '/invoices'>)
+    .parameter(0)
+    .toHaveProperty('select')
+    .toEqualTypeOf<((search: { page: number }) => unknown) | undefined>()
+
   expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/'>,
-  ).returns.toEqualTypeOf<{}>()
+    useSearch<DefaultRouter, '/invoices', false>,
+  ).returns.toEqualTypeOf<{ page?: number }>()
+
+  expectTypeOf(useSearch<DefaultRouter, '/invoices', false>)
+    .parameter(0)
+    .toHaveProperty('select')
+    .toEqualTypeOf<((search: { page?: number }) => unknown) | undefined>()
 
   expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/invoices'>,
-  ).returns.toEqualTypeOf<{ page: number }>()
+    useSearch<DefaultRouter, '/invoices', false, number>,
+  ).returns.toEqualTypeOf<number>()
 
-  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/invoices'>)
+  expectTypeOf(
+    useSearch<DefaultRouter, '/invoices', false, { func: () => void }>,
+  )
     .parameter(0)
     .toHaveProperty('select')
     .toEqualTypeOf<
-      ((search: { page: number }) => { page: number }) | undefined
+      ((search: { page?: number }) => { func: () => void }) | undefined
     >()
 
   expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/invoices', false>,
-  ).returns.toEqualTypeOf<{ page?: number }>()
+    useSearch<DefaultRouter, '/invoices', false, { func: () => void }>,
+  )
+    .parameter(0)
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<false | undefined>()
 
-  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/invoices', false>)
+  expectTypeOf(
+    useSearch<DefaultRouter, '/invoices', false, { func: () => void }, true>,
+  )
     .parameter(0)
     .toHaveProperty('select')
     .toEqualTypeOf<
-      ((search: { page?: number }) => { page?: number }) | undefined
+      | ((search: { page?: number }) => {
+          func: 'Function is not serializable'
+        })
+      | undefined
     >()
 
+  expectTypeOf(
+    useSearch<DefaultRouter, '/invoices', false, { func: () => void }, true>,
+  )
+    .parameter(0)
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<false | undefined>()
+
+  expectTypeOf(useSearch<DefaultRouter, '/invoices', false, { hi: any }, true>)
+    .parameter(0)
+    .toHaveProperty('select')
+    .toEqualTypeOf<
+      | ((search: { page?: number }) => {
+          hi: never
+        })
+      | undefined
+    >()
+
+  expectTypeOf(useSearch<DefaultRouter, '/invoices', false, { hi: any }, true>)
+    .parameter(0)
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<false | undefined>()
+
+  const routerWithStructuralSharing = createRouter({
+    routeTree,
+    defaultStructuralSharing: true,
+  })
+
   expectTypeOf(
     useSearch<
-      DefaultRouter['routeTree'],
+      typeof routerWithStructuralSharing,
       '/invoices',
       false,
-      FullSearchSchema<DefaultRouter['routeTree']>,
-      number
+      { func: () => void }
     >,
-  ).returns.toEqualTypeOf<number>()
+  )
+    .parameter(0)
+    .toHaveProperty('select')
+    .toEqualTypeOf<
+      | ((search: { page?: number }) => {
+          func: 'Function is not serializable'
+        })
+      | undefined
+    >()
+
+  expectTypeOf(
+    useSearch<
+      typeof routerWithStructuralSharing,
+      '/invoices',
+      false,
+      { date: () => void },
+      true
+    >,
+  )
+    .parameter(0)
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<false>()
+
+  expectTypeOf(
+    useSearch<
+      typeof routerWithStructuralSharing,
+      '/invoices',
+      false,
+      { hi: any },
+      true
+    >,
+  )
+    .parameter(0)
+    .toHaveProperty('select')
+    .toEqualTypeOf<
+      | ((search: { page?: number }) => {
+          hi: never
+        })
+      | undefined
+    >()
+
+  expectTypeOf(
+    useSearch<
+      typeof routerWithStructuralSharing,
+      '/invoices',
+      false,
+      { hi: any },
+      true
+    >,
+  )
+    .parameter(0)
+    .toHaveProperty('structuralSharing')
+    .toEqualTypeOf<false>()
 })
 
 test('when there are multiple search params', () => {
@@ -170,40 +276,33 @@ test('when there are multiple search params', () => {
     indexRoute,
   ])
 
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const defaultRouter = createRouter({
     routeTree,
   })
 
   type DefaultRouter = typeof defaultRouter
 
-  expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/'>,
-  ).returns.toEqualTypeOf<{}>()
+  expectTypeOf(useSearch<DefaultRouter, '/'>).returns.toEqualTypeOf<{}>()
 
-  expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/invoices'>,
-  ).returns.toEqualTypeOf<{ page: number }>()
+  expectTypeOf(useSearch<DefaultRouter, '/invoices'>).returns.toEqualTypeOf<{
+    page: number
+  }>()
 
-  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/invoices'>)
+  expectTypeOf(useSearch<DefaultRouter, '/invoices'>)
     .parameter(0)
     .toHaveProperty('select')
-    .toEqualTypeOf<
-      ((search: { page: number }) => { page: number }) | undefined
-    >()
+    .toEqualTypeOf<((search: { page: number }) => unknown) | undefined>()
 
   expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/invoices', false>,
+    useSearch<DefaultRouter, '/invoices', false>,
   ).returns.toEqualTypeOf<{ page?: number; detail?: string }>()
 
-  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/invoices', false>)
+  expectTypeOf(useSearch<DefaultRouter, '/invoices', false>)
     .parameter(0)
     .toHaveProperty('select')
     .toEqualTypeOf<
-      | ((search: { page?: number; detail?: string }) => {
-          page?: number
-          detail?: string
-        })
-      | undefined
+      ((search: { page?: number; detail?: string }) => unknown) | undefined
     >()
 })
 
@@ -244,33 +343,26 @@ test('when there are overlapping search params', () => {
 
   type DefaultRouter = typeof defaultRouter
 
-  expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/'>,
-  ).returns.toEqualTypeOf<{}>()
+  expectTypeOf(useSearch<DefaultRouter, '/'>).returns.toEqualTypeOf<{}>()
 
-  expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/invoices'>,
-  ).returns.toEqualTypeOf<{ page: number }>()
+  expectTypeOf(useSearch<DefaultRouter, '/invoices'>).returns.toEqualTypeOf<{
+    page: number
+  }>()
 
-  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/invoices'>)
+  expectTypeOf(useSearch<DefaultRouter, '/invoices'>)
     .parameter(0)
     .toHaveProperty('select')
-    .toEqualTypeOf<
-      ((search: { page: number }) => { page: number }) | undefined
-    >()
+    .toEqualTypeOf<((search: { page: number }) => unknown) | undefined>()
 
   expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/invoices', false>,
+    useSearch<DefaultRouter, '/invoices', false>,
   ).returns.toEqualTypeOf<{ page?: number; detail?: 'detail' | 50 }>()
 
-  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/invoices', false>)
+  expectTypeOf(useSearch<DefaultRouter, '/invoices', false>)
     .parameter(0)
     .toHaveProperty('select')
     .toEqualTypeOf<
-      | ((search: { page?: number; detail?: 'detail' | 50 }) => {
-          page?: number
-          detail?: 'detail' | 50
-        })
+      | ((search: { page?: number; detail?: 'detail' | 50 }) => unknown)
       | undefined
     >()
 })
@@ -304,39 +396,36 @@ test('when the root has no search params but the index route does', () => {
     indexRoute,
   ])
 
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const defaultRouter = createRouter({
     routeTree,
   })
 
   type DefaultRouter = typeof defaultRouter
 
-  expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/'>,
-  ).returns.toEqualTypeOf<{ indexPage: number }>()
+  expectTypeOf(useSearch<DefaultRouter, '/'>).returns.toEqualTypeOf<{
+    indexPage: number
+  }>()
 
-  expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '__root__'>,
-  ).returns.toEqualTypeOf<{}>()
+  expectTypeOf(useSearch<DefaultRouter, '__root__'>).returns.toEqualTypeOf<{}>()
 
   expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/invoices'>,
+    useSearch<DefaultRouter, '/invoices'>,
   ).returns.toEqualTypeOf<{}>()
 
-  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/invoices'>)
+  expectTypeOf(useSearch<DefaultRouter, '/invoices'>)
     .parameter(0)
     .toHaveProperty('select')
-    .toEqualTypeOf<((search: {}) => {}) | undefined>()
+    .toEqualTypeOf<((search: {}) => unknown) | undefined>()
 
   expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/invoices', false>,
+    useSearch<DefaultRouter, '/invoices', false>,
   ).returns.toEqualTypeOf<{ indexPage?: number }>()
 
-  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/invoices', false>)
+  expectTypeOf(useSearch<DefaultRouter, '/invoices', false>)
     .parameter(0)
     .toHaveProperty('select')
-    .toEqualTypeOf<
-      ((search: { indexPage?: number }) => { indexPage?: number }) | undefined
-    >()
+    .toEqualTypeOf<((search: { indexPage?: number }) => unknown) | undefined>()
 })
 
 test('when the root has search params but the index route does not', () => {
@@ -369,41 +458,38 @@ test('when the root has search params but the index route does not', () => {
     indexRoute,
   ])
 
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const defaultRouter = createRouter({
     routeTree,
   })
 
   type DefaultRouter = typeof defaultRouter
 
-  expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/'>,
-  ).returns.toEqualTypeOf<{ rootPage: number }>()
+  expectTypeOf(useSearch<DefaultRouter, '/'>).returns.toEqualTypeOf<{
+    rootPage: number
+  }>()
 
-  expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '__root__'>,
-  ).returns.toEqualTypeOf<{ rootPage: number }>()
+  expectTypeOf(useSearch<DefaultRouter, '__root__'>).returns.toEqualTypeOf<{
+    rootPage: number
+  }>()
 
-  expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/invoices'>,
-  ).returns.toEqualTypeOf<{ rootPage: number }>()
+  expectTypeOf(useSearch<DefaultRouter, '/invoices'>).returns.toEqualTypeOf<{
+    rootPage: number
+  }>()
 
-  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/invoices'>)
+  expectTypeOf(useSearch<DefaultRouter, '/invoices'>)
     .parameter(0)
     .toHaveProperty('select')
-    .toEqualTypeOf<
-      ((search: { rootPage: number }) => { rootPage: number }) | undefined
-    >()
+    .toEqualTypeOf<((search: { rootPage: number }) => unknown) | undefined>()
 
   expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/invoices', false>,
+    useSearch<DefaultRouter, '/invoices', false>,
   ).returns.toEqualTypeOf<{ rootPage?: number }>()
 
-  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/invoices', false>)
+  expectTypeOf(useSearch<DefaultRouter, '/invoices', false>)
     .parameter(0)
     .toHaveProperty('select')
-    .toEqualTypeOf<
-      ((search: { rootPage?: number }) => { rootPage?: number }) | undefined
-    >()
+    .toEqualTypeOf<((search: { rootPage?: number }) => unknown) | undefined>()
 })
 
 test('when the root has search params but the index does', () => {
@@ -437,43 +523,40 @@ test('when the root has search params but the index does', () => {
     indexRoute,
   ])
 
+  // eslint-disable-next-line unused-imports/no-unused-vars
   const defaultRouter = createRouter({
     routeTree,
   })
 
   type DefaultRouter = typeof defaultRouter
 
-  expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/'>,
-  ).returns.toEqualTypeOf<{ rootPage: number; indexPage: number }>()
+  expectTypeOf(useSearch<DefaultRouter, '/'>).returns.toEqualTypeOf<{
+    rootPage: number
+    indexPage: number
+  }>()
 
-  expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '__root__'>,
-  ).returns.toEqualTypeOf<{ rootPage: number }>()
+  expectTypeOf(useSearch<DefaultRouter, '__root__'>).returns.toEqualTypeOf<{
+    rootPage: number
+  }>()
 
-  expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/invoices'>,
-  ).returns.toEqualTypeOf<{ rootPage: number }>()
+  expectTypeOf(useSearch<DefaultRouter, '/invoices'>).returns.toEqualTypeOf<{
+    rootPage: number
+  }>()
 
-  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/invoices'>)
+  expectTypeOf(useSearch<DefaultRouter, '/invoices'>)
     .parameter(0)
     .toHaveProperty('select')
-    .toEqualTypeOf<
-      ((search: { rootPage: number }) => { rootPage: number }) | undefined
-    >()
+    .toEqualTypeOf<((search: { rootPage: number }) => unknown) | undefined>()
 
   expectTypeOf(
-    useSearch<DefaultRouter['routeTree'], '/invoices', false>,
+    useSearch<DefaultRouter, '/invoices', false>,
   ).returns.toEqualTypeOf<{ indexPage?: number; rootPage?: number }>()
 
-  expectTypeOf(useSearch<DefaultRouter['routeTree'], '/invoices', false>)
+  expectTypeOf(useSearch<DefaultRouter, '/invoices', false>)
     .parameter(0)
     .toHaveProperty('select')
     .toEqualTypeOf<
-      | ((search: { indexPage?: number; rootPage?: number }) => {
-          indexPage?: number
-          rootPage?: number
-        })
+      | ((search: { indexPage?: number; rootPage?: number }) => unknown)
       | undefined
     >()
 })
@@ -490,8 +573,9 @@ test('when a route has search params using SearchSchemaInput', () => {
   })
 
   const routeTree = rootRoute.addChildren([indexRoute])
-
-  expectTypeOf(useSearch<typeof routeTree, '/'>).returns.toEqualTypeOf<{
+  // eslint-disable-next-line unused-imports/no-unused-vars
+  const router = createRouter({ routeTree })
+  expectTypeOf(useSearch<typeof router, '/'>).returns.toEqualTypeOf<{
     page: number
   }>
 })
diff --git a/packages/router-arktype-adapter/tests/index.test-d.ts b/packages/router-arktype-adapter/tests/index.test-d.ts
index d17d29c40..3370bb3cd 100644
--- a/packages/router-arktype-adapter/tests/index.test-d.ts
+++ b/packages/router-arktype-adapter/tests/index.test-d.ts
@@ -48,8 +48,12 @@ test('when creating a route with arktype validation', () => {
     .parameter(0)
     .toEqualTypeOf<{ page: number } | { page: number; indexPage: number }>()
 
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page: number }>()
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page: number }>
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: number
+  }>()
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: number
+  }>
 })
 
 test('when using arktype schema with function', () => {
@@ -90,6 +94,10 @@ test('when using arktype schema with function', () => {
     .parameter(0)
     .toEqualTypeOf<{ page: number } | { page: number; indexPage: number }>()
 
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page: number }>()
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page: number }>
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: number
+  }>()
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: number
+  }>
 })
diff --git a/packages/router-valibot-adapter/tests/index.test-d.ts b/packages/router-valibot-adapter/tests/index.test-d.ts
index 7b96f04b6..710bd2375 100644
--- a/packages/router-valibot-adapter/tests/index.test-d.ts
+++ b/packages/router-valibot-adapter/tests/index.test-d.ts
@@ -48,8 +48,12 @@ test('when creating a route with valibot validation', () => {
     .parameter(0)
     .toEqualTypeOf<{ page: number } | { page: number; indexPage: number }>()
 
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page: number }>()
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page: number }>
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: number
+  }>()
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: number
+  }>
 })
 
 test('when using valibot schema with function', () => {
@@ -96,6 +100,10 @@ test('when using valibot schema with function', () => {
     .parameter(0)
     .toEqualTypeOf<{ page: number } | { page: number; indexPage: number }>()
 
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page: number }>()
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page: number }>
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: number
+  }>()
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: number
+  }>
 })
diff --git a/packages/router-zod-adapter/tests/index.test-d.ts b/packages/router-zod-adapter/tests/index.test-d.ts
index 6cdbb272e..6dd5b81d0 100644
--- a/packages/router-zod-adapter/tests/index.test-d.ts
+++ b/packages/router-zod-adapter/tests/index.test-d.ts
@@ -48,8 +48,12 @@ test('when creating a route with zod validation', () => {
     .parameter(0)
     .toEqualTypeOf<{ page: number } | { page: number; indexPage: number }>()
 
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page: number }>()
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page: number }>
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: number
+  }>()
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: number
+  }>
 })
 
 test('when creating a route with zod validation where input is output', () => {
@@ -93,8 +97,12 @@ test('when creating a route with zod validation where input is output', () => {
     .parameter(0)
     .toEqualTypeOf<{ page: number } | { page: number; indexPage: number }>()
 
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page: number }>()
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page: number }>
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: number
+  }>()
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: number
+  }>
 })
 
 test('when creating a route with zod validation where output is input', () => {
@@ -139,8 +147,12 @@ test('when creating a route with zod validation where output is input', () => {
     .parameter(0)
     .toEqualTypeOf<{ page?: number } | { page?: number; indexPage?: number }>()
 
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page?: number }>()
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page?: number }>
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page?: number
+  }>()
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page?: number
+  }>
 })
 
 test('when using zod schema without adapter', () => {
@@ -179,8 +191,12 @@ test('when using zod schema without adapter', () => {
     .parameter(0)
     .toEqualTypeOf<{ page: number } | { page: number; indexPage: number }>()
 
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page: number }>()
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page: number }>
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: number
+  }>()
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: number
+  }>
 })
 
 test('when using zod schema with function', () => {
@@ -225,6 +241,10 @@ test('when using zod schema with function', () => {
     .parameter(0)
     .toEqualTypeOf<{ page: number } | { page: number; indexPage: number }>()
 
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page: number }>()
-  expectTypeOf(rootRoute.useSearch()).toEqualTypeOf<{ page: number }>
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: number
+  }>()
+  expectTypeOf(rootRoute.useSearch<typeof router>()).toEqualTypeOf<{
+    page: number
+  }>
 })