Repository: TanStack/router
Pull request #1453 (2024-04-09 22:00:12)

perf: improve performance of Link and routes
### TS Performance Improvments

## Purpose

Type checking large code bases with many `Route`'s and `Link`'s can get quite sluggish. This PR introduces a number of performance optimizations I've discovered while profiling on the large file based example. The purpose of this PR is to improve suggestion times for such code bases and build times

## Extended Diagnostics

On main the following diagnostics are present for the example large file based

```
> tsc --extendedDiagnostics

Files:                         589
Lines of Library:            38118
Lines of Definitions:        77132
Lines of TypeScript:         12777
Lines of JavaScript:             0
Lines of JSON:                   0
Lines of Other:                  0
Identifiers:                113478
Symbols:                    351283
Types:                       62925
Instantiations:            1541766
Memory used:               311542K
Assignability cache size:   113357
Identity cache size:          3707
Subtype cache size:              0
Strict subtype cache size:  162006
I/O Read time:               0.08s
Parse time:                  0.55s
ResolveModule time:          0.11s
ResolveTypeReference time:   0.01s
ResolveLibrary time:         0.02s
Program time:                0.84s
Bind time:                   0.24s
Check time:                  5.64s
printTime time:              0.00s
Emit time:                   0.00s
Total time:                  6.72s
```

After the changes introduced in this PR the instantiations and check time are reduced

```
> tsc --extendedDiagnostics

Files:                         589
Lines of Library:            38118
Lines of Definitions:        77133
Lines of TypeScript:         12777
Lines of JavaScript:             0
Lines of JSON:                   0
Lines of Other:                  0
Identifiers:                113461
Symbols:                    189848
Types:                       50776
Instantiations:             592405
Memory used:               222842K
Assignability cache size:    34415
Identity cache size:          3718
Subtype cache size:              0
Strict subtype cache size:       0
I/O Read time:               0.07s
Parse time:                  0.54s
ResolveModule time:          0.11s
ResolveTypeReference time:   0.01s
ResolveLibrary time:         0.02s
Program time:                0.83s
Bind time:                   0.23s
Check time:                  3.82s
printTime time:              0.00s
Emit time:                   0.00s
Total time:                  4.88s
```

## Profiling

This is the profile for the large file route on main.

![image](https://github.com/TanStack/router/assets/7883601/542a6399-1b33-41ab-952e-a8b35ba67ead)

Going deep into each wall of the profile. The biggest wall is the route tree definition. There is a number of things to improve here.

- Performance of each individual file route types
- Variance checks on each Route
- Type parameter constraints on each Route
- Improving performance of merging properties from each parent route
- Only `Expand` when necessary and not eagerly

![image](https://github.com/TanStack/router/assets/7883601/3c73a3c3-1f2e-416c-a85c-f4aace9fd88a)

And the next significant thing is each `Link`. Each `Link` can be improved by the following:

- Separating `LinkComponentProps` to cache props for a component passed to `createLink`
- Use the resolved route for relative paths to narrow down the union of paths relevant for autocomplete
- Use `CheckPath` directly on the `to` prop to avoid an intersection
- Evaluate less of `params` and `search` until the user use these props

![image](https://github.com/TanStack/router/assets/7883601/a7b84d57-e0c6-437c-b0a2-cced0c9ddc1a)

After the performance improvements the route tree looks more like this

![image](https://github.com/TanStack/router/assets/7883601/86a2626b-1776-4eff-8cf8-eddde4cffc1e)

and each `Link` looks like this

![image](https://github.com/TanStack/router/assets/7883601/6d3a9c3c-d9b3-408f-9783-aaca02f7f583)

`Link` may seem like a small improvement for one `Link` but when you have many in large code bases this quickly accumulates


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

CI is running for commit 8d9b136e94410cb51e83d9f09202ffaad730f6ee.

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


---

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

Patch:
diff --git a/packages/react-router/src/Matches.tsx b/packages/react-router/src/Matches.tsx
index 5a0e47cd8..77f5e4af5 100644
--- a/packages/react-router/src/Matches.tsx
+++ b/packages/react-router/src/Matches.tsx
@@ -603,10 +603,10 @@ export function useLoaderData<
     ...opts,
     select: (s) => {
       return typeof opts.select === 'function'
-        ? opts.select(s.loaderData)
+        ? opts.select(s.loaderData as TRouteMatch)
         : s.loaderData
     },
-  })
+  }) as TSelected
 }
 
 export function isServerSideError(error: unknown): error is {
diff --git a/packages/react-router/src/fileRoute.ts b/packages/react-router/src/fileRoute.ts
index 3dbce684f..0f97cc1e3 100644
--- a/packages/react-router/src/fileRoute.ts
+++ b/packages/react-router/src/fileRoute.ts
@@ -24,7 +24,7 @@ import type {
   TrimPathLeft,
   UpdatableRouteOptions,
 } from './route'
-import type { Assign, Expand, IsAny } from './utils'
+import type { Assign, IsAny } from './utils'
 import type { RouteMatch } from './Matches'
 import type { NoInfer } from '@tanstack/react-store'
 import type { RegisteredRouter } from './router'
@@ -67,7 +67,7 @@ export type RemoveUnderScores<T extends string> = Replace<
 >
 
 type RemoveRouteGroups<T extends string> =
-  T extends `${infer Before}(${infer RouteGroup})${infer After}`
+  T extends `${infer Before}(${string})${infer After}`
     ? RemoveRouteGroups<`${Before}${After}`>
     : T
 
@@ -76,32 +76,22 @@ type NormalizeSlashes<T extends string> =
     ? NormalizeSlashes<`${Before}/${After}`>
     : T
 
-type ReplaceFirstOccurrence<
-  TValue extends string,
-  TSearch extends string,
-  TReplacement extends string,
-> = TValue extends `${infer Prefix}${TSearch}${infer Suffix}`
-  ? `${Prefix}${TReplacement}${Suffix}`
-  : TValue
-
 export type ResolveFilePath<
   TParentRoute extends AnyRoute,
   TFilePath extends string,
 > = TParentRoute['id'] extends RootRouteId
   ? TrimPathLeft<TFilePath>
-  : ReplaceFirstOccurrence<
-      TrimPathLeft<TFilePath>,
-      TrimPathLeft<TParentRoute['types']['customId']>,
-      ''
-    >
+  : TFilePath extends `${TParentRoute['types']['customId']}${infer TRest}`
+    ? TRest
+    : TFilePath
 
 export type FileRoutePath<
   TParentRoute extends AnyRoute,
   TFilePath extends string,
   TResolvedFilePath = ResolveFilePath<TParentRoute, TFilePath>,
-> = TResolvedFilePath extends `_${infer _}`
+> = TResolvedFilePath extends `_${string}`
   ? ''
-  : TResolvedFilePath extends `/_${infer _}`
+  : TResolvedFilePath extends `/_${string}`
     ? ''
     : TResolvedFilePath
 
@@ -154,34 +144,27 @@ export class FileRoute<
   createRoute = <
     TSearchSchemaInput extends RouteConstraints['TSearchSchema'] = {},
     TSearchSchema extends RouteConstraints['TSearchSchema'] = {},
-    TSearchSchemaUsed extends Record<
-      string,
-      any
-    > = TSearchSchemaInput extends SearchSchemaInput
+    TSearchSchemaUsed = TSearchSchemaInput extends SearchSchemaInput
       ? Omit<TSearchSchemaInput, keyof SearchSchemaInput>
       : TSearchSchema,
-    TFullSearchSchemaInput extends
-      RouteConstraints['TFullSearchSchema'] = ResolveFullSearchSchemaInput<
+    TFullSearchSchemaInput = ResolveFullSearchSchemaInput<
       TParentRoute,
       TSearchSchemaUsed
     >,
     TFullSearchSchema = ResolveFullSearchSchema<TParentRoute, TSearchSchema>,
-    TParams extends RouteConstraints['TParams'] = Expand<
-      Record<ParsePathParams<TPath>, string>
-    >,
-    TAllParams extends RouteConstraints['TAllParams'] = MergeFromFromParent<
+    TParams = Record<ParsePathParams<TPath>, string>,
+    TAllParams = MergeFromFromParent<
       TParentRoute['types']['allParams'],
       TParams
     >,
     TRouteContextReturn extends
       RouteConstraints['TRouteContext'] = RouteContext,
-    TRouteContext extends RouteConstraints['TRouteContext'] = [
-      TRouteContextReturn,
-    ] extends [never]
+    TRouteContext = [TRouteContextReturn] extends [never]
       ? RouteContext
       : TRouteContextReturn,
-    TAllContext = Expand<
-      Assign<IsAny<TParentRoute['types']['allContext'], {}>, TRouteContext>
+    TAllContext = Assign<
+      IsAny<TParentRoute['types']['allContext'], {}>,
+      TRouteContext
     >,
     TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
     TLoaderDeps extends Record<string, any> = {},
@@ -190,7 +173,6 @@ export class FileRoute<
       ? undefined
       : TLoaderDataReturn,
     TChildren extends RouteConstraints['TChildren'] = unknown,
-    TRouteTree extends RouteConstraints['TRouteTree'] = AnyRoute,
   >(
     options?: FileBaseRouteOptions<
       TParentRoute,
@@ -228,8 +210,7 @@ export class FileRoute<
     TLoaderDeps,
     TLoaderDataReturn,
     TLoaderData,
-    TChildren,
-    TRouteTree
+    TChildren
   > => {
     warning(
       this.silent,
@@ -294,7 +275,7 @@ export class LazyRoute<TRoute extends AnyRoute> {
 
   useMatch = <
     TRouteMatchState = RouteMatch<
-      TRoute['types']['routeTree'],
+      RegisteredRouter['routeTree'],
       TRoute['types']['id']
     >,
     TSelected = TRouteMatchState,
diff --git a/packages/react-router/src/index.tsx b/packages/react-router/src/index.tsx
index 3dc8f5a03..6e50e605b 100644
--- a/packages/react-router/src/index.tsx
+++ b/packages/react-router/src/index.tsx
@@ -67,7 +67,6 @@ export {
   type ActiveOptions,
   type LinkOptions,
   type CheckPath,
-  type CheckPathError,
   type ResolveRelativePath,
   type UseLinkPropsOptions,
   type ActiveLinkOptions,
diff --git a/packages/react-router/src/link.tsx b/packages/react-router/src/link.tsx
index f379937a4..a8f829685 100644
--- a/packages/react-router/src/link.tsx
+++ b/packages/react-router/src/link.tsx
@@ -84,67 +84,78 @@ export type RemoveLeadingSlashes<T> = T extends `/${infer R}`
   ? RemoveLeadingSlashes<R>
   : T
 
+export type ResolvePaths<TRouteTree extends AnyRoute, TSearchPath> =
+  RouteByPath<TRouteTree, RemoveTrailingSlashes<TSearchPath>> extends never
+    ? RoutePaths<TRouteTree>
+    : RoutePaths<RouteByPath<TRouteTree, RemoveTrailingSlashes<TSearchPath>>>
+
 export type SearchPaths<
-  TPaths,
+  TRouteTree extends AnyRoute,
   TSearchPath extends string,
+  TPaths = ResolvePaths<TRouteTree, TSearchPath>,
 > = TPaths extends `${RemoveTrailingSlashes<TSearchPath>}/${infer TRest}`
   ? TRest
   : never
 
 export type SearchRelativePathAutoComplete<
+  TRouteTree extends AnyRoute,
   TTo extends string,
   TSearchPath extends string,
-  TPaths,
-  TSearchedPaths = SearchPaths<TPaths, TSearchPath>,
-> = TSearchedPaths extends string ? `${TTo}/${TSearchedPaths}` : never
+> = `${TTo}/${SearchPaths<TRouteTree, TSearchPath>}`
 
 export type RelativeToParentPathAutoComplete<
+  TRouteTree extends AnyRoute,
   TFrom extends string,
   TTo extends string,
-  TPaths,
   TResolvedPath extends string = RemoveTrailingSlashes<
     ResolveRelativePath<TFrom, TTo>
   >,
 > =
-  | SearchRelativePathAutoComplete<TTo, TResolvedPath, TPaths>
+  | SearchRelativePathAutoComplete<TRouteTree, TTo, TResolvedPath>
   | (TResolvedPath extends '' ? never : `${TTo}/../`)
 
 export type RelativeToCurrentPathAutoComplete<
+  TRouteTree extends AnyRoute,
   TFrom extends string,
   TTo extends string,
   TRestTo extends string,
-  TPaths,
   TResolvedPath extends
     string = RemoveTrailingSlashes<`${RemoveTrailingSlashes<TFrom>}/${RemoveLeadingSlashes<TRestTo>}`>,
-> = SearchRelativePathAutoComplete<TTo, TResolvedPath, TPaths>
+> = SearchRelativePathAutoComplete<TRouteTree, TTo, TResolvedPath>
 
-export type AbsolutePathAutoComplete<TFrom extends string, TPaths> =
+export type AbsolutePathAutoComplete<
+  TRouteTree extends AnyRoute,
+  TFrom extends string,
+> =
   | (string extends TFrom
       ? './'
       : TFrom extends `/`
         ? never
-        : SearchPaths<TPaths, TFrom> extends ''
+        : SearchPaths<TRouteTree, TFrom> extends ''
           ? never
           : './')
   | (string extends TFrom ? '../' : TFrom extends `/` ? never : '../')
-  | TPaths
-  | (TFrom extends '/' ? never : SearchPaths<TPaths, TFrom>)
+  | RoutePaths<TRouteTree>
+  | (TFrom extends '/' ? never : SearchPaths<TRouteTree, TFrom>)
 
 export type RelativeToPathAutoComplete<
   TRouteTree extends AnyRoute,
   TFrom extends string,
   TTo extends string,
-  TPaths = RoutePaths<TRouteTree>,
 > = TTo extends `..${string}`
-  ? RelativeToParentPathAutoComplete<TFrom, RemoveTrailingSlashes<TTo>, TPaths>
+  ? RelativeToParentPathAutoComplete<
+      TRouteTree,
+      TFrom,
+      RemoveTrailingSlashes<TTo>
+    >
   : TTo extends `./${infer TRestTTo}`
     ? RelativeToCurrentPathAutoComplete<
+        TRouteTree,
         TFrom,
         RemoveTrailingSlashes<TTo>,
-        TRestTTo,
-        TPaths
+        TRestTTo
       >
-    : AbsolutePathAutoComplete<TFrom, TPaths>
+    : AbsolutePathAutoComplete<TRouteTree, TFrom>
 
 export type NavigateOptions<
   TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
@@ -183,19 +194,19 @@ export type ToSubOptions<
   TFrom extends RoutePaths<TRouteTree> | string = string,
   TTo extends string = '',
 > = {
-  to?: ToPathOption<TRouteTree, TFrom, TTo>
+  to?: ToPathOption<TRouteTree, TFrom, TTo> & {}
   hash?: true | Updater<string>
   state?: true | NonNullableUpdater<HistoryState>
   // The source route path. This is automatically set when using route-level APIs, but for type-safe relative routing on the router itself, this is required
-  from?: RoutePathsAutoComplete<TRouteTree, TFrom>
+  from?: RoutePathsAutoComplete<TRouteTree, TFrom> & {}
   // // When using relative route paths, this option forces resolution from the current path, instead of the route API's path or `from` path
-} & CheckPath<TRouteTree, {}, TFrom, TTo> &
-  SearchParamOptions<TRouteTree, TFrom, TTo> &
+} & SearchParamOptions<TRouteTree, TFrom, TTo> &
   PathParamOptions<TRouteTree, TFrom, TTo>
 
 type ParamsReducer<TFrom, TTo> = TTo | ((current: TFrom) => TTo)
 
 type ParamVariant = 'PATH' | 'SEARCH'
+
 type ExcludeRootSearchSchema<T, TExcluded = Exclude<T, RootSearchSchema>> = [
   TExcluded,
 ] extends [never]
@@ -224,49 +235,147 @@ type PostProcessParams<
   TParamVariant extends ParamVariant,
 > = TParamVariant extends 'SEARCH' ? ExcludeRootSearchSchema<T> : T
 
-export type ParamOptions<
+type ResolveFromParams<
   TRouteTree extends AnyRoute,
-  TFrom,
-  TTo extends string,
   TParamVariant extends ParamVariant,
+  TFrom,
   TFromRouteType extends
     | 'allParams'
     | 'fullSearchSchema' = TParamVariant extends 'PATH'
     ? 'allParams'
     : 'fullSearchSchema',
-  TToRouteType extends
-    | 'allParams'
-    | 'fullSearchSchemaInput' = TParamVariant extends 'PATH'
+> = PostProcessParams<
+  RouteByPath<TRouteTree, TFrom>['types'][TFromRouteType],
+  TParamVariant
+>
+
+type ResolveToParams<
+  TRouteTree extends AnyRoute,
+  TParamVariant extends ParamVariant,
+  TFrom,
+  TTo,
+  TRoute extends AnyRoute = ResolveRoute<TRouteTree, TFrom, TTo>,
+> = PostProcessParams<
+  TRoute['types'][TParamVariant extends 'PATH'
     ? 'allParams'
-    : 'fullSearchSchemaInput',
-  TFromParams = PostProcessParams<
-    RouteByPath<TRouteTree, TFrom>['types'][TFromRouteType],
-    TParamVariant
-  >,
-  TToParams = PostProcessParams<
-    ResolveRoute<TRouteTree, TFrom, TTo>['types'][TToRouteType],
-    TParamVariant
-  >,
-  TRelativeToParams = TParamVariant extends 'SEARCH'
+    : 'fullSearchSchemaInput'],
+  TParamVariant
+>
+
+type ResolveRelativeToParams<
+  TRouteTree extends AnyRoute,
+  TParamVariant extends ParamVariant,
+  TFrom,
+  TTo,
+  TToParams = ResolveToParams<TRouteTree, TParamVariant, TFrom, TTo>,
+> = TParamVariant extends 'SEARCH'
+  ? TToParams
+  : string extends TFrom
     ? TToParams
-    : true extends IsUnion<TFromParams>
-      ? TToParams
-      : MakeDifferenceOptional<TFromParams, TToParams>,
-  TReducer = ParamsReducer<TFromParams, TRelativeToParams>,
-> = keyof PickRequired<TRelativeToParams> extends never
-  ? Partial<MakeParamOption<TParamVariant, true | TReducer>>
-  : TFromParams extends Expand<WithoutEmpty<PickRequired<TRelativeToParams>>>
-    ? MakeParamOption<TParamVariant, true | TReducer>
-    : MakeParamOption<TParamVariant, TReducer>
-
-type MakeParamOption<
+    : MakeDifferenceOptional<
+        ResolveFromParams<TRouteTree, TParamVariant, TFrom>,
+        TToParams
+      >
+
+type MakeOptionalParams<
+  TRouteTree extends AnyRoute,
   TParamVariant extends ParamVariant,
-  T,
-> = TParamVariant extends 'PATH'
-  ? MakePathParamOptions<T>
-  : MakeSearchParamOptions<T>
-type MakeSearchParamOptions<T> = { search: T }
-type MakePathParamOptions<T> = { params: T }
+  TFrom,
+  TTo,
+> = TParamVariant extends 'SEARCH'
+  ? {
+      search?:
+        | true
+        | (ParamsReducer<
+            Expand<ResolveFromParams<TRouteTree, TParamVariant, TFrom>>,
+            Expand<
+              ResolveRelativeToParams<TRouteTree, TParamVariant, TFrom, TTo>
+            >
+          > & {})
+    }
+  : {
+      params?:
+        | true
+        | (ParamsReducer<
+            Expand<ResolveFromParams<TRouteTree, TParamVariant, TFrom>>,
+            Expand<
+              ResolveRelativeToParams<TRouteTree, TParamVariant, TFrom, TTo>
+            >
+          > & {})
+    }
+
+type MakeRequiredParamsReducer<
+  TRouteTree extends AnyRoute,
+  TParamVariant extends ParamVariant,
+  TFrom,
+  TToParams,
+  TFromParams = ResolveFromParams<TRouteTree, TParamVariant, TFrom>,
+> =
+  | ([TFromParams] extends [WithoutEmpty<PickRequired<TToParams>>]
+      ? true
+      : never)
+  | ParamsReducer<Expand<TFromParams>, TToParams>
+
+export type MakeRequiredParams<
+  TRouteTree extends AnyRoute,
+  TParamVariant extends ParamVariant,
+  TFrom,
+  TTo,
+> = TParamVariant extends 'SEARCH'
+  ? {
+      search: Expand<
+        MakeRequiredParamsReducer<
+          TRouteTree,
+          TParamVariant,
+          TFrom,
+          Expand<ResolveRelativeToParams<TRouteTree, TParamVariant, TFrom, TTo>>
+        >
+      > & {}
+    }
+  : {
+      params: Expand<
+        MakeRequiredParamsReducer<
+          TRouteTree,
+          TParamVariant,
+          TFrom,
+          Expand<ResolveRelativeToParams<TRouteTree, TParamVariant, TFrom, TTo>>
+        >
+      > & {}
+    }
+
+export type IsRequiredParams<TParams> = keyof TParams extends infer K extends
+  keyof TParams
+  ? K extends any
+    ? undefined extends TParams[K]
+      ? never
+      : true
+    : never
+  : never
+
+export type IsRequired<
+  TRouteTree extends AnyRoute,
+  TParamVariant extends ParamVariant,
+  TFrom,
+  TTo,
+> = string extends TTo
+  ? string extends TFrom
+    ? never
+    : IsRequiredParams<
+        ResolveRelativeToParams<TRouteTree, TParamVariant, TFrom, TTo>
+      >
+  : IsRequiredParams<
+      ResolveRelativeToParams<TRouteTree, TParamVariant, TFrom, TTo>
+    >
+
+export type ParamOptions<
+  TRouteTree extends AnyRoute,
+  TFrom,
+  TTo extends string,
+  TParamVariant extends ParamVariant,
+> =
+  IsRequired<TRouteTree, TParamVariant, TFrom, TTo> extends never
+    ? MakeOptionalParams<TRouteTree, TParamVariant, TFrom, TTo>
+    : MakeRequiredParams<TRouteTree, TParamVariant, TFrom, TTo>
 
 export type SearchParamOptions<
   TRouteTree extends AnyRoute,
@@ -283,9 +392,9 @@ export type PathParamOptions<
 export type ToPathOption<
   TRouteTree extends AnyRoute = AnyRoute,
   TFrom extends RoutePaths<TRouteTree> | string = string,
-  TTo extends string = '',
+  TTo extends string = string,
 > =
-  | TTo
+  | CheckPath<TRouteTree, TTo, never, TFrom, TTo>
   | RelativeToPathAutoComplete<
       TRouteTree,
       NoInfer<TFrom> extends string ? NoInfer<TFrom> : '',
@@ -317,14 +426,8 @@ export type LinkOptions<
   disabled?: boolean
 }
 
-export type CheckPath<TRouteTree extends AnyRoute, TPass, TFrom, TTo> =
-  ResolveRoute<TRouteTree, TFrom, TTo> extends never
-    ? CheckPathError<TRouteTree>
-    : TPass
-
-export type CheckPathError<TRouteTree extends AnyRoute> = {
-  to: RoutePaths<TRouteTree>
-}
+export type CheckPath<TRouteTree extends AnyRoute, TPass, TFail, TFrom, TTo> =
+  ResolveRoute<TRouteTree, TFrom, TTo> extends never ? TFail : TPass
 
 export type ResolveRelativePath<TFrom, TTo = '.'> = TFrom extends string
   ? TTo extends string
@@ -634,7 +737,7 @@ export type ActiveLinkOptions<
 export type LinkProps<
   TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
   TFrom extends RoutePaths<TRouteTree> | string = string,
-  TTo extends string = '',
+  TTo extends string = string,
   TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
   TMaskTo extends string = '',
 > = ActiveLinkOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> & {
@@ -644,6 +747,23 @@ export type LinkProps<
     | ((state: { isActive: boolean }) => React.ReactNode)
 }
 
+type LinkComponentProps<TComp> = React.PropsWithoutRef<
+  TComp extends React.FC<infer TProps> | React.Component<infer TProps>
+    ? TProps
+    : TComp extends keyof JSX.IntrinsicElements
+      ? Omit<React.HTMLProps<TComp>, 'children' | 'preload'>
+      : never
+> &
+  React.RefAttributes<
+    TComp extends
+      | React.FC<{ ref: infer TRef }>
+      | React.Component<{ ref: infer TRef }>
+      ? TRef
+      : TComp extends keyof JSX.IntrinsicElements
+        ? React.ComponentRef<TComp>
+        : never
+  >
+
 export type LinkComponent<TComp> = <
   TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
   TFrom extends RoutePaths<TRouteTree> | string = string,
@@ -651,23 +771,8 @@ export type LinkComponent<TComp> = <
   TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
   TMaskTo extends string = '',
 >(
-  props: React.PropsWithoutRef<
-    LinkProps<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
-      (TComp extends React.FC<infer TProps> | React.Component<infer TProps>
-        ? TProps
-        : TComp extends keyof JSX.IntrinsicElements
-          ? Omit<React.HTMLProps<TComp>, 'children' | 'preload'>
-          : never)
-  > &
-    React.RefAttributes<
-      TComp extends
-        | React.FC<{ ref: infer TRef }>
-        | React.Component<{ ref: infer TRef }>
-        ? TRef
-        : TComp extends keyof JSX.IntrinsicElements
-          ? React.ComponentRef<TComp>
-          : never
-    >,
+  props: LinkProps<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
+    LinkComponentProps<TComp>,
 ) => React.ReactElement
 
 export function createLink<const TComp>(Comp: TComp): LinkComponent<TComp> {
diff --git a/packages/react-router/src/route.ts b/packages/react-router/src/route.ts
index 8c1799912..f8a19d3fa 100644
--- a/packages/react-router/src/route.ts
+++ b/packages/react-router/src/route.ts
@@ -5,6 +5,7 @@ import { useParams } from './useParams'
 import { useSearch } from './useSearch'
 import { notFound } from './not-found'
 import { useNavigate } from './useNavigate'
+import type { UseNavigateResult } from './useNavigate'
 import type * as React from 'react'
 import type { RouteMatch } from './Matches'
 import type { AnyRouteMatch } from './Matches'
@@ -59,13 +60,13 @@ export type RouteOptions<
   TPath extends string = string,
   TSearchSchemaInput extends Record<string, any> = {},
   TSearchSchema extends Record<string, any> = {},
-  TSearchSchemaUsed extends Record<string, any> = {},
-  TFullSearchSchemaInput extends Record<string, any> = TSearchSchemaUsed,
+  TSearchSchemaUsed = {},
+  TFullSearchSchemaInput = TSearchSchemaUsed,
   TFullSearchSchema = TSearchSchema,
-  TParams extends AnyPathParams = AnyPathParams,
-  TAllParams extends AnyPathParams = TParams,
+  TParams = AnyPathParams,
+  TAllParams = TParams,
   TRouteContextReturn extends RouteContext = RouteContext,
-  TRouteContext extends RouteContext = RouteContext,
+  TRouteContext = RouteContext,
   TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
   TAllContext = AnyContext,
   TLoaderDeps extends Record<string, any> = {},
@@ -108,10 +109,10 @@ export type FileBaseRouteOptions<
   TSearchSchemaInput extends Record<string, any> = {},
   TSearchSchema extends Record<string, any> = {},
   TFullSearchSchema = TSearchSchema,
-  TParams extends AnyPathParams = {},
+  TParams = {},
   TAllParams = ParamsFallback<TPath, TParams>,
   TRouteContextReturn extends RouteContext = RouteContext,
-  TRouteContext extends RouteContext = RouteContext,
+  TRouteContext = RouteContext,
   TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
   TAllContext = AnyContext,
   TLoaderDeps extends Record<string, any> = {},
@@ -171,13 +172,13 @@ export type BaseRouteOptions<
   TPath extends string = string,
   TSearchSchemaInput extends Record<string, any> = {},
   TSearchSchema extends Record<string, any> = {},
-  TSearchSchemaUsed extends Record<string, any> = {},
-  TFullSearchSchemaInput extends Record<string, any> = TSearchSchemaUsed,
+  TSearchSchemaUsed = {},
+  TFullSearchSchemaInput = TSearchSchemaUsed,
   TFullSearchSchema = TSearchSchema,
-  TParams extends AnyPathParams = {},
+  TParams = {},
   TAllParams = ParamsFallback<TPath, TParams>,
   TRouteContextReturn extends RouteContext = RouteContext,
-  TRouteContext extends RouteContext = RouteContext,
+  TRouteContext = RouteContext,
   TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
   TAllContext = AnyContext,
   TLoaderDeps extends Record<string, any> = {},
@@ -202,12 +203,12 @@ export type BaseRouteOptions<
   }
 
 type BeforeLoadFn<
-  TFullSearchSchema,
-  TParentRoute extends AnyRoute,
-  TAllParams,
+  in out TFullSearchSchema,
+  in out TParentRoute extends AnyRoute,
+  in out TAllParams,
   TRouteContextReturn extends RouteContext,
-  TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
-  TContext = IsAny<TParentRoute['types']['allContext'], TRouterContext>,
+  in out TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
+  in out TContext = IsAny<TParentRoute['types']['allContext'], TRouterContext>,
 > = (opts: {
   search: TFullSearchSchema
   abortController: AbortController
@@ -220,51 +221,48 @@ type BeforeLoadFn<
   cause: 'preload' | 'enter' | 'stay'
 }) => Promise<TRouteContextReturn> | TRouteContextReturn | void
 
-export type UpdatableRouteOptions<
-  TAllParams extends Record<string, any>,
-  TFullSearchSchema,
-  TLoaderData,
-> = {
-  // test?: (args: TAllContext) => void
-  // If true, this route will be matched as case-sensitive
-  caseSensitive?: boolean
-  // If true, this route will be forcefully wrapped in a suspense boundary
-  wrapInSuspense?: boolean
-  // The content to be rendered when the route is matched. If no component is provided, defaults to `<Outlet />`
-  component?: RouteComponent
-  errorComponent?: false | null | ErrorRouteComponent
-  notFoundComponent?: NotFoundRouteComponent
-  pendingComponent?: RouteComponent
-  pendingMs?: number
-  pendingMinMs?: number
-  staleTime?: number
-  gcTime?: number
-  preloadStaleTime?: number
-  preloadGcTime?: number
-  // Filter functions that can manipulate search params *before* they are passed to links and navigate
-  // calls that match this route.
-  preSearchFilters?: Array<SearchFilter<TFullSearchSchema>>
-  // Filter functions that can manipulate search params *after* they are passed to links and navigate
-  // calls that match this route.
-  postSearchFilters?: Array<SearchFilter<TFullSearchSchema>>
-  onError?: (err: any) => void
-  // These functions are called as route matches are loaded, stick around and leave the active
-  // matches
-  onEnter?: (match: AnyRouteMatch) => void
-  onStay?: (match: AnyRouteMatch) => void
-  onLeave?: (match: AnyRouteMatch) => void
-  meta?: (ctx: {
-    params: TAllParams
-    loaderData: TLoaderData
-  }) =>
-    | Array<JSX.IntrinsicElements['meta']>
-    | Promise<Array<JSX.IntrinsicElements['meta']>>
-  links?: () => Array<JSX.IntrinsicElements['link']>
-  scripts?: () => Array<JSX.IntrinsicElements['script']>
-  headers?: (ctx: {
-    loaderData: TLoaderData
-  }) => Promise<Record<string, string>> | Record<string, string>
-} & UpdatableStaticRouteOption
+export type UpdatableRouteOptions<TAllParams, TFullSearchSchema, TLoaderData> =
+  {
+    // test?: (args: TAllContext) => void
+    // If true, this route will be matched as case-sensitive
+    caseSensitive?: boolean
+    // If true, this route will be forcefully wrapped in a suspense boundary
+    wrapInSuspense?: boolean
+    // The content to be rendered when the route is matched. If no component is provided, defaults to `<Outlet />`
+    component?: RouteComponent
+    errorComponent?: false | null | ErrorRouteComponent
+    notFoundComponent?: NotFoundRouteComponent
+    pendingComponent?: RouteComponent
+    pendingMs?: number
+    pendingMinMs?: number
+    staleTime?: number
+    gcTime?: number
+    preloadStaleTime?: number
+    preloadGcTime?: number
+    // Filter functions that can manipulate search params *before* they are passed to links and navigate
+    // calls that match this route.
+    preSearchFilters?: Array<SearchFilter<TFullSearchSchema>>
+    // Filter functions that can manipulate search params *after* they are passed to links and navigate
+    // calls that match this route.
+    postSearchFilters?: Array<SearchFilter<TFullSearchSchema>>
+    onError?: (err: any) => void
+    // These functions are called as route matches are loaded, stick around and leave the active
+    // matches
+    onEnter?: (match: AnyRouteMatch) => void
+    onStay?: (match: AnyRouteMatch) => void
+    onLeave?: (match: AnyRouteMatch) => void
+    meta?: (ctx: {
+      params: TAllParams
+      loaderData: TLoaderData
+    }) =>
+      | Array<JSX.IntrinsicElements['meta']>
+      | Promise<Array<JSX.IntrinsicElements['meta']>>
+    links?: () => Array<JSX.IntrinsicElements['link']>
+    scripts?: () => Array<JSX.IntrinsicElements['script']>
+    headers?: (ctx: {
+      loaderData: TLoaderData
+    }) => Promise<Record<string, string>> | Record<string, string>
+  } & UpdatableStaticRouteOption
 
 export type UpdatableStaticRouteOption =
   {} extends PickRequired<StaticDataRouteOption>
@@ -323,26 +321,26 @@ export type SearchSchemaValidatorFn<TInput, TReturn> = (
 ) => TReturn
 
 export type RouteLoaderFn<
-  TAllParams = {},
-  TLoaderDeps extends Record<string, any> = {},
-  TAllContext = AnyContext,
-  TRouteContext extends Record<string, any> = AnyContext,
+  in out TAllParams = {},
+  in out TLoaderDeps extends Record<string, any> = {},
+  in out TAllContext = AnyContext,
+  in out TRouteContext = AnyContext,
   TLoaderData = unknown,
 > = (
   match: LoaderFnContext<TAllParams, TLoaderDeps, TAllContext, TRouteContext>,
 ) => Promise<TLoaderData> | TLoaderData
 
 export interface LoaderFnContext<
-  TAllParams = {},
-  TLoaderDeps = {},
-  TAllContext = AnyContext,
-  TRouteContext extends Record<string, any> = AnyContext,
+  in out TAllParams = {},
+  in out TLoaderDeps = {},
+  in out TAllContext = AnyContext,
+  in out TRouteContext = AnyContext,
 > {
   abortController: AbortController
   preload: boolean
   params: TAllParams
   deps: TLoaderDeps
-  context: Expand<Assign<TAllContext, TRouteContext>>
+  context: Assign<TAllContext, TRouteContext>
   location: ParsedLocation // Do not supply search schema here so as to demotivate people from trying to shortcut loaderDeps
   /**
    * @deprecated Use `throw redirect({ to: '/somewhere' })` instead
@@ -382,25 +380,21 @@ export type InferFullSearchSchemaInput<TRoute> = TRoute extends {
 export type ResolveFullSearchSchema<
   TParentRoute extends AnyRoute,
   TSearchSchema,
-> = Expand<
-  Assign<
-    TParentRoute['id'] extends RootRouteId
-      ? Omit<TParentRoute['types']['searchSchema'], keyof RootSearchSchema>
-      : TParentRoute['types']['fullSearchSchema'],
-    TSearchSchema
-  >
+> = Assign<
+  TParentRoute['id'] extends RootRouteId
+    ? Omit<TParentRoute['types']['searchSchema'], keyof RootSearchSchema>
+    : TParentRoute['types']['fullSearchSchema'],
+  TSearchSchema
 >
 
 export type ResolveFullSearchSchemaInput<
   TParentRoute extends AnyRoute,
   TSearchSchemaUsed,
-> = Expand<
-  Assign<
-    TParentRoute['id'] extends RootRouteId
-      ? Omit<TParentRoute['types']['searchSchemaInput'], keyof RootSearchSchema>
-      : TParentRoute['types']['fullSearchSchemaInput'],
-    TSearchSchemaUsed
-  >
+> = Assign<
+  TParentRoute['id'] extends RootRouteId
+    ? Omit<TParentRoute['types']['searchSchemaInput'], keyof RootSearchSchema>
+    : TParentRoute['types']['fullSearchSchemaInput'],
+  TSearchSchemaUsed
 >
 
 export interface AnyRoute
@@ -424,22 +418,16 @@ export interface AnyRoute
     any,
     any,
     any,
-    any,
     any
   > {}
 
 // eslint-disable-next-line @typescript-eslint/naming-convention
 export type MergeFromFromParent<T, U> = IsAny<T, U, T & U>
 
-export type ResolveAllParams<
-  TParentRoute extends AnyRoute,
-  TParams extends AnyPathParams,
-> =
+export type ResolveAllParams<TParentRoute extends AnyRoute, TParams> =
   Record<never, string> extends TParentRoute['types']['allParams']
     ? TParams
-    : Expand<
-        UnionToIntersection<TParentRoute['types']['allParams'] & TParams> & {}
-      >
+    : UnionToIntersection<TParentRoute['types']['allParams'] & TParams> & {}
 
 export type RouteConstraints = {
   TParentRoute: AnyRoute
@@ -463,9 +451,9 @@ export function getRouteApi<
   TId extends RouteIds<RegisteredRouter['routeTree']>,
   TRoute extends AnyRoute = RouteById<RegisteredRouter['routeTree'], TId>,
   TFullSearchSchema = TRoute['types']['fullSearchSchema'],
-  TAllParams extends AnyPathParams = TRoute['types']['allParams'],
+  TAllParams = TRoute['types']['allParams'],
   TAllContext = TRoute['types']['allContext'],
-  TLoaderDeps extends Record<string, any> = TRoute['types']['loaderDeps'],
+  TLoaderDeps = TRoute['types']['loaderDeps'],
   TLoaderData = TRoute['types']['loaderData'],
 >(id: TId) {
   return new RouteApi<
@@ -483,9 +471,9 @@ export class RouteApi<
   TId extends RouteIds<RegisteredRouter['routeTree']>,
   TRoute extends AnyRoute = RouteById<RegisteredRouter['routeTree'], TId>,
   TFullSearchSchema = TRoute['types']['fullSearchSchema'],
-  TAllParams extends AnyPathParams = TRoute['types']['allParams'],
+  TAllParams = TRoute['types']['allParams'],
   TAllContext = TRoute['types']['allContext'],
-  TLoaderDeps extends Record<string, any> = TRoute['types']['loaderDeps'],
+  TLoaderDeps = TRoute['types']['loaderDeps'],
   TLoaderData = TRoute['types']['loaderData'],
 > {
   id: TId
@@ -507,8 +495,8 @@ export class RouteApi<
     return useMatch({ select: opts?.select, from: this.id })
   }
 
-  useRouteContext = <TSelected = TAllContext>(opts?: {
-    select?: (s: TAllContext) => TSelected
+  useRouteContext = <TSelected = Expand<TAllContext>>(opts?: {
+    select?: (s: Expand<TAllContext>) => TSelected
   }): TSelected => {
     return useMatch({
       from: this.id,
@@ -516,14 +504,14 @@ export class RouteApi<
     })
   }
 
-  useSearch = <TSelected = TFullSearchSchema>(opts?: {
-    select?: (s: TFullSearchSchema) => TSelected
+  useSearch = <TSelected = Expand<TFullSearchSchema>>(opts?: {
+    select?: (s: Expand<TFullSearchSchema>) => TSelected
   }): TSelected => {
     return useSearch({ ...opts, from: this.id })
   }
 
-  useParams = <TSelected = TAllParams>(opts?: {
-    select?: (s: TAllParams) => TSelected
+  useParams = <TSelected = Expand<TAllParams>>(opts?: {
+    select?: (s: Expand<TAllParams>) => TSelected
   }): TSelected => {
     return useParams({ ...opts, from: this.id })
   }
@@ -550,55 +538,48 @@ export class RouteApi<
 }
 
 export class Route<
-  TParentRoute extends RouteConstraints['TParentRoute'] = AnyRoute,
+  in out TParentRoute extends RouteConstraints['TParentRoute'] = AnyRoute,
   in out TPath extends RouteConstraints['TPath'] = '/',
-  TFullPath extends RouteConstraints['TFullPath'] = ResolveFullPath<
+  in out TFullPath extends RouteConstraints['TFullPath'] = ResolveFullPath<
     TParentRoute,
     TPath
   >,
-  TCustomId extends RouteConstraints['TCustomId'] = string,
-  TId extends RouteConstraints['TId'] = ResolveId<
+  in out TCustomId extends RouteConstraints['TCustomId'] = string,
+  in out TId extends RouteConstraints['TId'] = ResolveId<
     TParentRoute,
     TCustomId,
     TPath
   >,
-  TSearchSchemaInput extends RouteConstraints['TSearchSchema'] = {},
-  TSearchSchema extends RouteConstraints['TSearchSchema'] = {},
-  TSearchSchemaUsed extends Record<
-    string,
-    any
-  > = TSearchSchemaInput extends SearchSchemaInput
+  in out TSearchSchemaInput extends RouteConstraints['TSearchSchema'] = {},
+  in out TSearchSchema extends RouteConstraints['TSearchSchema'] = {},
+  in out TSearchSchemaUsed = TSearchSchemaInput extends SearchSchemaInput
     ? Omit<TSearchSchemaInput, keyof SearchSchemaInput>
     : TSearchSchema,
-  TFullSearchSchemaInput extends Record<
-    string,
-    any
-  > = ResolveFullSearchSchemaInput<TParentRoute, TSearchSchemaUsed>,
-  TFullSearchSchema = ResolveFullSearchSchema<TParentRoute, TSearchSchema>,
-  TParams extends RouteConstraints['TParams'] = Expand<
-    Record<ParsePathParams<TPath>, string>
+  in out TFullSearchSchemaInput = ResolveFullSearchSchemaInput<
+    TParentRoute,
+    TSearchSchemaUsed
   >,
-  TAllParams extends RouteConstraints['TAllParams'] = ResolveAllParams<
+  in out TFullSearchSchema = ResolveFullSearchSchema<
     TParentRoute,
-    TParams
+    TSearchSchema
   >,
+  in out TParams = Record<ParsePathParams<TPath>, string>,
+  in out TAllParams = ResolveAllParams<TParentRoute, TParams>,
   TRouteContextReturn extends RouteConstraints['TRouteContext'] = RouteContext,
-  in out TRouteContext extends RouteConstraints['TRouteContext'] = [
-    TRouteContextReturn,
-  ] extends [never]
+  in out TRouteContext = [TRouteContextReturn] extends [never]
     ? RouteContext
     : TRouteContextReturn,
-  in out TAllContext = Expand<
-    Assign<IsAny<TParentRoute['types']['allContext'], {}>, TRouteContext>
+  in out TAllContext = Assign<
+    IsAny<TParentRoute['types']['allContext'], {}>,
+    TRouteContext
   >,
-  TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
-  TLoaderDeps extends Record<string, any> = {},
+  in out TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
+  in out TLoaderDeps extends Record<string, any> = {},
   TLoaderDataReturn = unknown,
-  TLoaderData = [TLoaderDataReturn] extends [never]
+  in out TLoaderData = [TLoaderDataReturn] extends [never]
     ? undefined
     : TLoaderDataReturn,
-  TChildren extends RouteConstraints['TChildren'] = unknown,
-  TRouteTree extends RouteConstraints['TRouteTree'] = AnyRoute,
+  in out TChildren extends RouteConstraints['TChildren'] = unknown,
 > {
   isRoot: TParentRoute extends Route<any> ? true : false
   options: RouteOptions<
@@ -687,13 +668,12 @@ export class Route<
     routeContext: TRouteContext
     allContext: TAllContext
     children: TChildren
-    routeTree: TRouteTree
     routerContext: TRouterContext
     loaderData: TLoaderData
     loaderDeps: TLoaderDeps
   }
 
-  init = (opts: { originalIndex: number }) => {
+  init = (opts: { originalIndex: number }): void => {
     this.originalIndex = opts.originalIndex
 
     const options = this.options as
@@ -768,7 +748,7 @@ export class Route<
     this.to = fullPath as TrimPathRight<TFullPath>
   }
 
-  addChildren = <TNewChildren extends Array<AnyRoute>>(
+  addChildren = <const TNewChildren extends ReadonlyArray<AnyRoute>>(
     children: TNewChildren,
   ): Route<
     TParentRoute,
@@ -790,8 +770,7 @@ export class Route<
     TLoaderDeps,
     TLoaderDataReturn,
     TLoaderData,
-    TNewChildren,
-    TRouteTree
+    TNewChildren
   > => {
     this.children = children as any
     return this as any
@@ -826,19 +805,18 @@ export class Route<
       TRouterContext,
       TLoaderDeps,
       TNewLoaderData,
-      TChildren,
-      TRouteTree
+      TChildren
     >
   }
 
   update = (
     options: UpdatableRouteOptions<TAllParams, TFullSearchSchema, TLoaderData>,
-  ) => {
+  ): this => {
     Object.assign(this.options, options)
     return this
   }
 
-  lazy = (lazyFn: () => Promise<LazyRoute<any>>) => {
+  lazy = (lazyFn: () => Promise<LazyRoute<any>>): this => {
     this.lazyFn = lazyFn
     return this
   }
@@ -854,8 +832,8 @@ export class Route<
     return useMatch({ ...opts, from: this.id })
   }
 
-  useRouteContext = <TSelected = TAllContext>(opts?: {
-    select?: (search: TAllContext) => TSelected
+  useRouteContext = <TSelected = Expand<TAllContext>>(opts?: {
+    select?: (search: Expand<TAllContext>) => TSelected
   }): TSelected => {
     return useMatch({
       ...opts,
@@ -864,14 +842,14 @@ export class Route<
     })
   }
 
-  useSearch = <TSelected = TFullSearchSchema>(opts?: {
-    select?: (search: TFullSearchSchema) => TSelected
+  useSearch = <TSelected = Expand<TFullSearchSchema>>(opts?: {
+    select?: (search: Expand<TFullSearchSchema>) => TSelected
   }): TSelected => {
     return useSearch({ ...opts, from: this.id })
   }
 
-  useParams = <TSelected = TAllParams>(opts?: {
-    select?: (search: TAllParams) => TSelected
+  useParams = <TSelected = Expand<TAllParams>>(opts?: {
+    select?: (search: Expand<TAllParams>) => TSelected
   }): TSelected => {
     return useParams({ ...opts, from: this.id })
   }
@@ -888,7 +866,7 @@ export class Route<
     return useLoaderData({ ...opts, from: this.id } as any)
   }
 
-  useNavigate = () => {
+  useNavigate = (): UseNavigateResult<string> => {
     return useNavigate({ from: this.id })
   }
 }
@@ -908,32 +886,23 @@ export function createRoute<
   >,
   TSearchSchemaInput extends RouteConstraints['TSearchSchema'] = {},
   TSearchSchema extends RouteConstraints['TSearchSchema'] = {},
-  TSearchSchemaUsed extends Record<
-    string,
-    any
-  > = TSearchSchemaInput extends SearchSchemaInput
+  TSearchSchemaUsed = TSearchSchemaInput extends SearchSchemaInput
     ? Omit<TSearchSchemaInput, keyof SearchSchemaInput>
     : TSearchSchema,
-  TFullSearchSchemaInput extends Record<
-    string,
-    any
-  > = ResolveFullSearchSchemaInput<TParentRoute, TSearchSchemaUsed>,
-  TFullSearchSchema = ResolveFullSearchSchema<TParentRoute, TSearchSchema>,
-  TParams extends RouteConstraints['TParams'] = Expand<
-    Record<ParsePathParams<TPath>, string>
-  >,
-  TAllParams extends RouteConstraints['TAllParams'] = ResolveAllParams<
+  TFullSearchSchemaInput = ResolveFullSearchSchemaInput<
     TParentRoute,
-    TParams
+    TSearchSchemaUsed
   >,
+  TFullSearchSchema = ResolveFullSearchSchema<TParentRoute, TSearchSchema>,
+  TParams = Record<ParsePathParams<TPath>, string>,
+  TAllParams = ResolveAllParams<TParentRoute, TParams>,
   TRouteContextReturn extends RouteConstraints['TRouteContext'] = RouteContext,
-  TRouteContext extends RouteConstraints['TRouteContext'] = [
-    TRouteContextReturn,
-  ] extends [never]
+  TRouteContext = [TRouteContextReturn] extends [never]
     ? RouteContext
     : TRouteContextReturn,
-  TAllContext = Expand<
-    Assign<IsAny<TParentRoute['types']['allContext'], {}>, TRouteContext>
+  TAllContext = Assign<
+    IsAny<TParentRoute['types']['allContext'], {}>,
+    TRouteContext
   >,
   TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
   TLoaderDeps extends Record<string, any> = {},
@@ -942,7 +911,6 @@ export function createRoute<
     ? undefined
     : TLoaderDataReturn,
   TChildren extends RouteConstraints['TChildren'] = unknown,
-  TRouteTree extends RouteConstraints['TRouteTree'] = AnyRoute,
 >(
   options: RouteOptions<
     TParentRoute,
@@ -984,8 +952,7 @@ export function createRoute<
     TLoaderDeps,
     TLoaderDataReturn,
     TLoaderData,
-    TChildren,
-    TRouteTree
+    TChildren
   >(options)
 }
 
@@ -1008,7 +975,7 @@ export function createRootRouteWithContext<TRouterContext extends {}>() {
   >(
     options?: Omit<
       RouteOptions<
-        AnyRoute, // TParentRoute
+        any, // TParentRoute
         RootRouteId, // TCustomId
         '', // TPath
         TSearchSchemaInput, // TSearchSchemaInput
@@ -1021,7 +988,7 @@ export function createRootRouteWithContext<TRouterContext extends {}>() {
         TRouteContextReturn, // TRouteContextReturn
         TRouteContext, // TRouteContext
         TRouterContext,
-        Expand<Assign<TRouterContext, TRouteContext>>, // TAllContext
+        Assign<TRouterContext, TRouteContext>, // TAllContext
         TLoaderDeps,
         TLoaderDataReturn, // TLoaderDataReturn,
         TLoaderData // TLoaderData,
@@ -1057,17 +1024,19 @@ export type RootSearchSchema = {
 }
 
 export class RootRoute<
-  TSearchSchemaInput extends Record<string, any> = RootSearchSchema,
-  TSearchSchema extends Record<string, any> = RootSearchSchema,
-  TSearchSchemaUsed extends Record<string, any> = RootSearchSchema,
+  in out TSearchSchemaInput extends Record<string, any> = RootSearchSchema,
+  in out TSearchSchema extends Record<string, any> = RootSearchSchema,
+  in out TSearchSchemaUsed extends Record<string, any> = RootSearchSchema,
   TRouteContextReturn extends RouteContext = RouteContext,
-  TRouteContext extends RouteContext = [TRouteContextReturn] extends [never]
+  in out TRouteContext extends RouteContext = [TRouteContextReturn] extends [
+    never,
+  ]
     ? RouteContext
     : TRouteContextReturn,
-  TRouterContext extends {} = {},
+  in out TRouterContext extends {} = {},
   TLoaderDeps extends Record<string, any> = {},
   TLoaderDataReturn = unknown,
-  TLoaderData = [TLoaderDataReturn] extends [never]
+  in out TLoaderData = [TLoaderDataReturn] extends [never]
     ? undefined
     : TLoaderDataReturn,
 > extends Route<
@@ -1090,8 +1059,7 @@ export class RootRoute<
   TLoaderDeps,
   TLoaderDataReturn,
   TLoaderData,
-  any, // TChildren
-  any // TRouteTree
+  any // TChildren
 > {
   /**
    * @deprecated `RootRoute` is now an internal implementation detail. Use `createRootRoute()` instead.
@@ -1099,7 +1067,7 @@ export class RootRoute<
   constructor(
     options?: Omit<
       RouteOptions<
-        AnyRoute, // TParentRoute
+        any, // TParentRoute
         RootRouteId, // TCustomId
         '', // TPath
         TSearchSchemaInput, // TSearchSchemaInput
@@ -1146,7 +1114,7 @@ export function createRootRoute<
 >(
   options?: Omit<
     RouteOptions<
-      AnyRoute, // TParentRoute
+      any, // TParentRoute
       RootRouteId, // TCustomId
       '', // TPath
       TSearchSchemaInput, // TSearchSchemaInput
@@ -1295,8 +1263,9 @@ export class NotFoundRoute<
   TFullSearchSchema = ResolveFullSearchSchema<TParentRoute, TSearchSchema>,
   TRouteContextReturn extends RouteConstraints['TRouteContext'] = AnyContext,
   TRouteContext extends RouteConstraints['TRouteContext'] = RouteContext,
-  TAllContext = Expand<
-    Assign<IsAny<TParentRoute['types']['allContext'], {}>, TRouteContext>
+  TAllContext = Assign<
+    IsAny<TParentRoute['types']['allContext'], {}>,
+    TRouteContext
   >,
   TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
   TLoaderDeps extends Record<string, any> = {},
@@ -1305,7 +1274,6 @@ export class NotFoundRoute<
     ? undefined
     : TLoaderDataReturn,
   TChildren extends RouteConstraints['TChildren'] = unknown,
-  TRouteTree extends RouteConstraints['TRouteTree'] = AnyRoute,
 > extends Route<
   TParentRoute,
   '/404',
@@ -1326,8 +1294,7 @@ export class NotFoundRoute<
   TLoaderDeps,
   TLoaderDataReturn,
   TLoaderData,
-  TChildren,
-  TRouteTree
+  TChildren
 > {
   constructor(
     options: Omit<
diff --git a/packages/react-router/src/routeInfo.ts b/packages/react-router/src/routeInfo.ts
index da3820c7a..f643d41fb 100644
--- a/packages/react-router/src/routeInfo.ts
+++ b/packages/react-router/src/routeInfo.ts
@@ -4,7 +4,7 @@ import type { Expand, UnionToIntersection, UnionToTuple } from './utils'
 export type ParseRoute<TRouteTree, TAcc = TRouteTree> = TRouteTree extends {
   types: { children: infer TChildren }
 }
-  ? TChildren extends Array<unknown>
+  ? TChildren extends ReadonlyArray<unknown>
     ? ParseRoute<TChildren[number], TAcc | TChildren[number]>
     : TAcc
   : TAcc
@@ -14,7 +14,7 @@ export type RoutesById<TRouteTree extends AnyRoute> = {
 }
 
 export type RouteById<TRouteTree extends AnyRoute, TId> = Extract<
-  Extract<ParseRoute<TRouteTree>, { id: TId }>,
+  RoutesById<TRouteTree>[TId],
   AnyRoute
 >
 
@@ -56,11 +56,9 @@ type Reduce<TValue extends Array<any>, TResult = unknown> = TValue extends [
   : TResult
 
 export type FullSearchSchema<TRouteTree extends AnyRoute> = Partial<
-  Expand<
-    Reduce<UnionToTuple<ParseRoute<TRouteTree>['types']['fullSearchSchema']>>
-  >
+  Reduce<UnionToTuple<ParseRoute<TRouteTree>['types']['fullSearchSchema']>>
 >
 
-export type AllParams<TRouteTree extends AnyRoute> = Expand<
-  UnionToIntersection<ParseRoute<TRouteTree>['types']['allParams']>
+export type AllParams<TRouteTree extends AnyRoute> = UnionToIntersection<
+  ParseRoute<TRouteTree>['types']['allParams']
 >
diff --git a/packages/react-router/src/router.ts b/packages/react-router/src/router.ts
index edb7dca25..5198a4325 100644
--- a/packages/react-router/src/router.ts
+++ b/packages/react-router/src/router.ts
@@ -261,9 +261,9 @@ export function createRouter<
 }
 
 export class Router<
-  TRouteTree extends AnyRoute = AnyRoute,
-  TDehydrated extends Record<string, any> = Record<string, any>,
-  TSerializedError extends Record<string, any> = Record<string, any>,
+  in out TRouteTree extends AnyRoute = AnyRoute,
+  in out TDehydrated extends Record<string, any> = Record<string, any>,
+  in out TSerializedError extends Record<string, any> = Record<string, any>,
 > {
   // Option-independent properties
   tempLocationKey: string | undefined = `${Math.round(
@@ -1268,8 +1268,8 @@ export class Router<
             preload: !!preload,
             context: parentContext,
             location,
-            navigate: (opts) =>
-              this.navigate({ ...opts, from: match.pathname } as any),
+            navigate: (opts: any) =>
+              this.navigate({ ...opts, from: match.pathname }),
             buildLocation: this.buildLocation,
             cause: preload ? 'preload' : match.cause,
           })) ?? ({} as any)
diff --git a/packages/react-router/src/useParams.tsx b/packages/react-router/src/useParams.tsx
index ba9ecf99e..608e43f1c 100644
--- a/packages/react-router/src/useParams.tsx
+++ b/packages/react-router/src/useParams.tsx
@@ -21,7 +21,7 @@ export function useParams<
   return useMatch({
     ...opts,
     select: (match) => {
-      return opts.select ? opts.select(match.params) : match.params
+      return opts.select ? opts.select(match.params as TParams) : match.params
     },
-  })
+  }) as TSelected
 }
diff --git a/packages/react-router/src/utils.ts b/packages/react-router/src/utils.ts
index 3f6321a46..9370dda09 100644
--- a/packages/react-router/src/utils.ts
+++ b/packages/react-router/src/utils.ts
@@ -15,12 +15,14 @@ export type PickRequired<T> = {
 }
 
 // from https://stackoverflow.com/a/76458160
-export type WithoutEmpty<T> = T extends T ? ({} extends T ? never : T) : never
+export type WithoutEmpty<T> = T extends any ? ({} extends T ? never : T) : never
 
 // export type Expand<T> = T
 export type Expand<T> = T extends object
   ? T extends infer O
-    ? { [K in keyof O]: O[K] }
+    ? O extends Function
+      ? O
+      : { [K in keyof O]: O[K] }
     : never
   : T
 
@@ -36,10 +38,12 @@ export type DeepPartial<T> = T extends object
     }
   : T
 
-// eslint-disable-next-line @typescript-eslint/naming-convention
-export type MakeDifferenceOptional<T, U> = Omit<U, keyof T> &
-  Partial<Pick<U, keyof T & keyof U>> &
-  PickRequired<Omit<U, keyof PickRequired<T>>>
+export type MakeDifferenceOptional<TLeft, TRight> = Omit<
+  TRight,
+  keyof TLeft
+> & {
+  [K in keyof TLeft & keyof TRight]?: TRight[K]
+}
 
 // from https://stackoverflow.com/a/53955431
 // eslint-disable-next-line @typescript-eslint/naming-convention


Test patch:
diff --git a/packages/react-router/tests/link.test-d.tsx b/packages/react-router/tests/link.test-d.tsx
index 0b5019240..12eee7530 100644
--- a/packages/react-router/tests/link.test-d.tsx
+++ b/packages/react-router/tests/link.test-d.tsx
@@ -51,11 +51,19 @@ const invoiceDetailsRoute = createRoute({
   validateSearch: (): { page?: number } => ({ page: 0 }),
 })
 
+const detailRoute = createRoute({
+  getParentRoute: () => invoiceDetailsRoute,
+  path: '$detailId',
+})
+
 const routeTree = rootRoute.addChildren([
   postsRoute.addChildren([postRoute, postsIndexRoute]),
   invoicesRoute.addChildren([
     invoicesIndexRoute,
-    invoiceRoute.addChildren([invoiceEditRoute, invoiceDetailsRoute]),
+    invoiceRoute.addChildren([
+      invoiceEditRoute,
+      invoiceDetailsRoute.addChildren([detailRoute]),
+    ]),
   ]),
   indexRoute,
 ])
@@ -63,12 +71,6 @@ const routeTree = rootRoute.addChildren([
 type RouteTree = typeof routeTree
 
 test('when navigating to the root', () => {
-  const thing = expectTypeOf(Link<RouteTree, string, '/'>)
-    //
-    .parameter(0)
-    .toHaveProperty('to')
-    .extract()
-
   expectTypeOf(Link<RouteTree, string, '/'>)
     .parameter(0)
     .toHaveProperty('to')
@@ -81,6 +83,7 @@ test('when navigating to the root', () => {
       | '/invoices/'
       | '/invoices/$invoiceId'
       | '/invoices/$invoiceId/details'
+      | '/invoices/$invoiceId/details/$detailId'
       | '/invoices/$invoiceId/edit'
       | '/posts'
       | '/posts/'
@@ -89,6 +92,7 @@ test('when navigating to the root', () => {
       | 'invoices/'
       | 'invoices/$invoiceId'
       | 'invoices/$invoiceId/details'
+      | 'invoices/$invoiceId/details/$detailId'
       | 'invoices/$invoiceId/edit'
       | 'posts'
       | 'posts/'
@@ -111,6 +115,7 @@ test('when navigating from a route with no params and no search to the root', ()
       | '/invoices/$invoiceId'
       | '/invoices/$invoiceId/details'
       | '/invoices/$invoiceId/edit'
+      | '/invoices/$invoiceId/details/$detailId'
       | '/posts'
       | '/posts/'
       | '/posts/$postId'
@@ -137,6 +142,7 @@ test('when navigating from a route with no params and no search to the parent ro
       | '../invoices/$invoiceId'
       | '../invoices/$invoiceId/edit'
       | '../invoices/$invoiceId/details'
+      | '../invoices/$invoiceId/details/$detailId'
       | '../invoices'
       | '../invoices/'
       | '../'
@@ -160,6 +166,7 @@ test('from autocompletes to all absolute routes', () => {
       | '/invoices/$invoiceId'
       | '/invoices/$invoiceId/edit'
       | '/invoices/$invoiceId/details'
+      | '/invoices/$invoiceId/details/$detailId'
       | undefined
     >()
 })
@@ -232,7 +239,12 @@ test('when navigating to a route with params', () => {
   params.returns.toEqualTypeOf<{ postId: string }>()
   params
     .parameter(0)
-    .toEqualTypeOf<{} | { invoiceId: string } | { postId: string }>()
+    .toEqualTypeOf<
+      | {}
+      | { invoiceId: string }
+      | { postId: string }
+      | { invoiceId: string; detailId: string }
+    >()
 })
 
 test('when navigating from a route with no params to a route with params', () => {
@@ -242,11 +254,9 @@ test('when navigating from a route with no params to a route with params', () =>
 
   const params = expectTypeOf(TestLink).parameter(0).toHaveProperty('params')
 
-  params
-    .exclude<Function | boolean>()
-    .branded.toEqualTypeOf<{ invoiceId: string }>()
+  params.exclude<Function | boolean>().toEqualTypeOf<{ invoiceId: string }>()
 
-  params.returns.branded.toEqualTypeOf<{ invoiceId: string }>()
+  params.returns.toEqualTypeOf<{ invoiceId: string }>()
   params.parameter(0).toEqualTypeOf<{}>()
 })
 
@@ -258,9 +268,43 @@ test('when navigating from a route to a route with the same params', () => {
 
   params
     .exclude<Function | boolean>()
-    .branded.toEqualTypeOf<{ invoiceId?: string | undefined } | undefined>()
+    .toEqualTypeOf<{ invoiceId?: string | undefined } | undefined>()
+
+  params.returns.toEqualTypeOf<{ invoiceId?: string | undefined }>()
+  params.parameter(0).toEqualTypeOf<{ invoiceId: string }>()
+})
+
+test('when navigating from a route with params to a route with different params', () => {
+  const TestLink = Link<
+    RouteTree,
+    '/invoices/$invoiceId',
+    '../../posts/$postId'
+  >
+  const params = expectTypeOf(TestLink).parameter(0).toHaveProperty('params')
+
+  expectTypeOf(TestLink).parameter(0).toMatchTypeOf<{ params: unknown }>()
+
+  params.exclude<Function | boolean>().toEqualTypeOf<{ postId: string }>()
+
+  params.returns.toEqualTypeOf<{ postId: string }>()
+  params.parameter(0).toEqualTypeOf<{ invoiceId: string }>()
+})
+
+test('when navigating from a route with params to a route with an additional param', () => {
+  const TestLink = Link<
+    RouteTree,
+    '/invoices/$invoiceId',
+    './details/$detailId'
+  >
+  const params = expectTypeOf(TestLink).parameter(0).toHaveProperty('params')
+
+  expectTypeOf(TestLink).parameter(0).toMatchTypeOf<{ params: unknown }>()
+
+  params
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ invoiceId?: string | undefined; detailId: string }>()
 
-  params.returns.branded.toEqualTypeOf<{ invoiceId?: string | undefined }>()
+  params.returns.toEqualTypeOf<{ invoiceId?: string; detailId: string }>()
   params.parameter(0).toEqualTypeOf<{ invoiceId: string }>()
 })
 
@@ -278,13 +322,16 @@ test('when navigating to a union of routes with params', () => {
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ invoiceId: string } | { postId: string } | undefined>()
 
-  params.returns.branded.toEqualTypeOf<
-    { invoiceId: string } | { postId: string }
-  >()
+  params.returns.toEqualTypeOf<{ invoiceId: string } | { postId: string }>()
 
   params
     .parameter(0)
-    .toEqualTypeOf<{} | { invoiceId: string } | { postId: string }>()
+    .toEqualTypeOf<
+      | {}
+      | { invoiceId: string }
+      | { postId: string }
+      | { invoiceId: string; detailId: string }
+    >()
 })
 
 test('when navigating to a union of routes including the root', () => {
@@ -303,13 +350,18 @@ test('when navigating to a union of routes including the root', () => {
       { invoiceId: string } | { postId: string } | {} | undefined
     >()
 
-  params.returns.branded.toEqualTypeOf<
+  params.returns.toEqualTypeOf<
     { invoiceId: string } | { postId: string } | {}
   >()
 
   params
     .parameter(0)
-    .toEqualTypeOf<{} | { invoiceId: string } | { postId: string }>()
+    .toEqualTypeOf<
+      | {}
+      | { invoiceId: string }
+      | { postId: string }
+      | { invoiceId: string; detailId: string }
+    >()
 })
 
 test('when navigating from a route with search params to the same route', () => {
@@ -354,7 +406,7 @@ test('when navigating from a route with no search params to a route with search
 
   expectTypeOf(TestLink).parameter(0).toMatchTypeOf<{ search: unknown }>()
   params.exclude<Function | boolean>().toEqualTypeOf<{ page: number }>()
-  params.returns.branded.toEqualTypeOf<{ page: number }>()
+  params.returns.toEqualTypeOf<{ page: number }>()
   params.parameter(0).toEqualTypeOf<{}>()
 })
 
@@ -372,7 +424,7 @@ test('when navigating to a union of routes with search params', () => {
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ page: number } | {} | undefined>()
 
-  params.returns.branded.toEqualTypeOf<{ page: number } | {}>()
+  params.returns.toEqualTypeOf<{ page: number } | {}>()
 
   params.parameter(0).toEqualTypeOf<{} | { page: number } | { page?: number }>()
 })
diff --git a/packages/react-router/tests/route.test-d.tsx b/packages/react-router/tests/route.test-d.tsx
index 4e1f0a086..671d73249 100644
--- a/packages/react-router/tests/route.test-d.tsx
+++ b/packages/react-router/tests/route.test-d.tsx
@@ -16,22 +16,21 @@ test('when creating the root', () => {
 test('when creating the root route with context', () => {
   const createRouteResult = createRootRouteWithContext<{ userId: string }>()
 
-  const rootRoute = createRouteResult()
-
-  expectTypeOf(rootRoute.options.loader)
-    .parameter(0)
-    .toMatchTypeOf<{ context: { userId: string } }>()
+  const rootRoute = createRouteResult({
+    loader: (opts) =>
+      expectTypeOf(opts).toMatchTypeOf<{ context: { userId: string } }>(),
+  })
 
   expectTypeOf(rootRoute.fullPath).toEqualTypeOf<'/'>()
   expectTypeOf(rootRoute.id).toEqualTypeOf<'__root__'>()
   expectTypeOf(rootRoute.path).toEqualTypeOf<'/'>()
 
-  expectTypeOf(rootRoute.useRouteContext()).branded.toEqualTypeOf<{
+  expectTypeOf(rootRoute.useRouteContext()).toEqualTypeOf<{
     userId: string
   }>()
   expectTypeOf(rootRoute.useRouteContext<string>)
     .parameter(0)
-    .branded.toEqualTypeOf<
+    .toEqualTypeOf<
       { select?: (search: { userId: string }) => string } | undefined
     >()
 })
@@ -57,7 +56,7 @@ test('when creating a child route from the root route with context', () => {
     getParentRoute: () => rootRoute,
   })
 
-  expectTypeOf(rootRoute.useRouteContext()).branded.toEqualTypeOf<{
+  expectTypeOf(rootRoute.useRouteContext()).toEqualTypeOf<{
     userId: string
   }>()
 
@@ -102,13 +101,12 @@ test('when creating a child route with a loader from the root route with context
   const invoicesRoute = createRoute({
     path: 'invoices',
     getParentRoute: () => rootRoute,
-    loader: async () => [{ id: 'invoice1' }, { id: 'invoice2' }] as const,
+    loader: async (opts) => {
+      expectTypeOf(opts).toMatchTypeOf<{ context: { userId: string } }>()
+      return [{ id: 'invoice1' }, { id: 'invoice2' }] as const
+    },
   })
 
-  expectTypeOf(invoicesRoute.options.loader)
-    .parameter(0)
-    .toMatchTypeOf<{ context: { userId: string } }>()
-
   expectTypeOf(invoicesRoute.useLoaderData<string>)
     .parameter(0)
     .toEqualTypeOf<
@@ -156,9 +154,8 @@ 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
-  }>()
+  expectTypeOf(invoicesRoute.useSearch()).toEqualTypeOf<{ page?: number }>()
+
   expectTypeOf(invoicesRoute.useSearch<number>)
     .parameter(0)
     .toEqualTypeOf<
@@ -185,67 +182,64 @@ test('when creating a child route with params from the root route', () => {
 test('when creating a child route with params, search and loader from the root route', () => {
   const rootRoute = createRootRoute()
 
-  const invoicesRoute = createRoute({
+  createRoute({
     path: 'invoices/$invoiceId',
     getParentRoute: () => rootRoute,
-    loader: () => ({ id: 'invoiceId1' }),
+    loader: (opts) =>
+      expectTypeOf(opts).toMatchTypeOf<{
+        params: { invoiceId: string }
+      }>,
     validateSearch: () => ({ page: 0 }),
   })
-
-  expectTypeOf(invoicesRoute.options.loader).parameter(0).toMatchTypeOf<{
-    params: { invoiceId: string }
-  }>()
 })
 
 test('when creating a child route with params, search, loader and loaderDeps from the root route', () => {
   const rootRoute = createRootRoute()
 
-  const invoicesRoute = createRoute({
+  createRoute({
     path: 'invoices/$invoiceId',
     getParentRoute: () => rootRoute,
-    loader: () => ({ id: 'invoiceId1' }),
-    validateSearch: () => ({ page: 0 }),
     loaderDeps: (deps) => ({ page: deps.search.page }),
+    loader: (opts) =>
+      expectTypeOf(opts).toMatchTypeOf<{
+        params: { invoiceId: string }
+        deps: { page: number }
+      }>(),
+    validateSearch: () => ({ page: 0 }),
   })
-
-  expectTypeOf(invoicesRoute.options.loader).parameter(0).toMatchTypeOf<{
-    params: { invoiceId: string }
-    deps: { page: number }
-  }>()
 })
 
 test('when creating a child route with params, search, loader and loaderDeps from the root route with context', () => {
   const rootRoute = createRootRouteWithContext<{ userId: string }>()()
 
-  const invoicesRoute = createRoute({
+  createRoute({
     path: 'invoices/$invoiceId',
     getParentRoute: () => rootRoute,
-    loader: () => ({ id: 'invoiceId1' }),
-    validateSearch: () => ({ page: 0 }),
     loaderDeps: (deps) => ({ page: deps.search.page }),
+    loader: (opts) =>
+      expectTypeOf(opts).toMatchTypeOf<{
+        params: { invoiceId: string }
+        deps: { page: number }
+        context: { userId: string }
+      }>(),
+    validateSearch: () => ({ page: 0 }),
   })
-
-  expectTypeOf(invoicesRoute.options.loader).parameter(0).toMatchTypeOf<{
-    params: { invoiceId: string }
-    deps: { page: number }
-    context: { userId: string }
-  }>()
 })
 
 test('when creating a child route with params, search with beforeLoad from the root route with context', () => {
   const rootRoute = createRootRouteWithContext<{ userId: string }>()()
 
-  const invoicesRoute = createRoute({
+  createRoute({
     path: 'invoices/$invoiceId',
     getParentRoute: () => rootRoute,
     validateSearch: () => ({ page: 0 }),
+    beforeLoad: (opts) =>
+      expectTypeOf(opts).toMatchTypeOf<{
+        params: { invoiceId: string }
+        context: { userId: string }
+        search: { page: number }
+      }>(),
   })
-
-  expectTypeOf(invoicesRoute.options.beforeLoad).parameter(0).toMatchTypeOf<{
-    params: { invoiceId: string }
-    context: { userId: string }
-    search: { page: number }
-  }>()
 })
 
 test('when creating a child route with params from a parent with params', () => {
@@ -373,17 +367,17 @@ test('when creating a child route with context, search, params, loaderDeps and l
       detailPage: deps.search.detailPage,
       invoicePage: deps.search.page,
     }),
+    loader: (opts) =>
+      expectTypeOf(opts).toMatchTypeOf<{
+        params: { detailId: string; invoiceId: string }
+        deps: { detailPage: number; invoicePage: number }
+        context: {
+          userId: string
+          detailsPermissions: readonly ['view']
+          invoicePermissions: readonly ['view']
+        }
+      }>(),
   })
-
-  expectTypeOf(detailRoute.options.loader).parameter(0).toMatchTypeOf<{
-    params: { detailId: string; invoiceId: string }
-    deps: { detailPage: number; invoicePage: number }
-    context: {
-      userId: string
-      detailsPermissions: readonly ['view']
-      invoicePermissions: readonly ['view']
-    }
-  }>()
 })
 
 test('when creating a child route with context, search, params and beforeLoad', () => {
@@ -408,18 +402,18 @@ test('when creating a child route with context, search, params and beforeLoad',
     beforeLoad: () => ({ detailsPermissions: ['view'] as const }),
   })
 
-  const detailRoute = createRoute({
+  createRoute({
     path: '$detailId',
     getParentRoute: () => detailsRoute,
+    beforeLoad: (opts) =>
+      expectTypeOf(opts).toMatchTypeOf<{
+        params: { detailId: string; invoiceId: string }
+        search: { detailPage: number; page: number }
+        context: {
+          userId: string
+          detailsPermissions: readonly ['view']
+          invoicePermissions: readonly ['view']
+        }
+      }>(),
   })
-
-  expectTypeOf(detailRoute.options.beforeLoad).parameter(0).toMatchTypeOf<{
-    params: { detailId: string; invoiceId: string }
-    search: { detailPage: number; page: number }
-    context: {
-      userId: string
-      detailsPermissions: readonly ['view']
-      invoicePermissions: readonly ['view']
-    }
-  }>()
 })