Repository: TanStack/router
Pull request #1564 (2024-05-06 14:10:26)

perf: geting lazy?, doing less work? generate more (plus some recommendations)
# Getting lazy? Doing less work? and generating more (plus some recommendations)

This PR focuses more on complex routes and to some degree many routes. When I say a complex route, I mean routes which have `search`, `params`, `loaders` and external dependencies such as `zod` or other heavy TS related dependencies.

A bigger issue I've noticed in TSR is in order to get suggestions, the route tree needs to be evaluated. If your routes contain complex things that TS needs to infer like validation schemas then this can slow down TS evaluating the route tree because its hard at work on the types of the validation libraries. But for most suggestions, we only need the `fullPath`, we don't need to work out the search schema or the params in order to get suggestions for `Link` for example until `search` is used. The ideal scenario is we do as little as possible in order to get the suggestions we need

## Show me the results!

I've updated our large filed based example to include:
- 100 routes for absolute pathing
- 100 routes for relative pathing
- 100 routes for params with loader
- 100 routes for search with loader

These include external dependencies which also do TS inference. Note the results may vary depending on what dependencies you use and how the types are written

On main:
```
> tsc --extendedDiagnostics

Files:                         636
Lines of Library:            38118
Lines of Definitions:        80134
Lines of TypeScript:         18481
Lines of JavaScript:             0
Lines of JSON:                   0
Lines of Other:                  0
Identifiers:                132113
Symbols:                    345367
Types:                      119980
Instantiations:            2720561
Memory used:               345796K
Assignability cache size:    56379
Identity cache size:          8114
Subtype cache size:           2022
Strict subtype cache size:       0
I/O Read time:               0.09s
Parse time:                  0.67s
ResolveModule time:          0.15s
ResolveTypeReference time:   0.00s
ResolveLibrary time:         0.02s
Program time:                1.03s
Bind time:                   0.29s
Check time:                  7.25s
printTime time:              0.00s
Emit time:                   0.00s
Total time:                  8.57s
```
On current branch:
```
> tsc --extendedDiagnostics

Files:                         636
Lines of Library:            38118
Lines of Definitions:        80143
Lines of TypeScript:         19712
Lines of JavaScript:             0
Lines of JSON:                   0
Lines of Other:                  0
Identifiers:                133346
Symbols:                    322837
Types:                      111429
Instantiations:             773068
Memory used:               327186K
Assignability cache size:    45204
Identity cache size:          8109
Subtype cache size:           2022
Strict subtype cache size:       0
I/O Read time:               0.08s
Parse time:                  0.59s
ResolveModule time:          0.12s
ResolveTypeReference time:   0.00s
ResolveLibrary time:         0.02s
Program time:                0.92s
Bind time:                   0.28s
Check time:                  5.51s
printTime time:              0.00s
Emit time:                   0.00s
Total time:                  6.71s
```

## Getting lazy? Doing less work?

In TSR one of the largest performance bottlenecks is the route tree building. This node is important to reduce because it is directly linked to TS server suggestion times. What is contributing to this node being so slow is firstly, a lot of work upfront to evaluate `search`, `loader`, `context` and the others, which are possibly inferred from external libraries.

The first interesting change this PR introduces is not a breaking one but introducing a new signature to `addChildren`. `addChildren` accepts a tuple of routes like the following:

`route.addChildren([childRoute1, childRoute2])`

`addChildren` also in addition can now accept an object `route.addChildren({ childRoute1, childRoute2 })`.

Why this change? Well, it looks like tuples seem to be forcing TS to be doing something in each route which is quite slow and it gets worse with different external dependencies, where objects seem to be more cheap or perhaps more lazy in nature, maybe there's also caching involved? Depending on the libraries used in for example `validateSearch`, you may notice a difference in performance improvements.

For example, using `valibot` instead of `zod` in the above example with `addChildren` that accept a tuple
```
> tsc --extendedDiagnostics

Files:                          638
Lines of Library:             38680
Lines of Definitions:         86278
Lines of TypeScript:          19809
Lines of JavaScript:              0
Lines of JSON:                    0
Lines of Other:                   0
Identifiers:                 141166
Symbols:                     270865
Types:                        90876
Instantiations:            22514251
Memory used:                310778K
Assignability cache size:     33466
Identity cache size:           7787
Subtype cache size:            2830
Strict subtype cache size:        1
I/O Read time:                0.09s
Parse time:                   0.63s
ResolveModule time:           0.14s
ResolveTypeReference time:    0.01s
ResolveLibrary time:          0.02s
Program time:                 1.03s
Bind time:                    0.30s
Check time:                  14.66s
printTime time:               0.00s
Emit time:                    0.00s
Total time:                  15.99s
```
Now with `addChildren` that accepts an object

```
> tsc --extendedDiagnostics

Files:                         638
Lines of Library:            38680
Lines of Definitions:        86278
Lines of TypeScript:         19813
Lines of JavaScript:             0
Lines of JSON:                   0
Lines of Other:                  0
Identifiers:                141170
Symbols:                    269855
Types:                       90640
Instantiations:            1093144
Memory used:               316056K
Assignability cache size:    33455
Identity cache size:          7787
Subtype cache size:           2830
Strict subtype cache size:       1
I/O Read time:               0.09s
Parse time:                  0.68s
ResolveModule time:          0.14s
ResolveTypeReference time:   0.01s
ResolveLibrary time:         0.02s
Program time:                1.09s
Bind time:                   0.35s
Check time:                  5.35s
printTime time:              0.00s
Emit time:                   0.00s
Total time:                  6.79s
```

That's a big difference between tuples and objects. I'm still not totally sure why tuples behave this way or if I'm even correct about my assumptions but to me it looks like tuples are forcing some kind of extra work the TS compiler is doing and its related to `validateSearch`  and how the types of the external library are likely written.

### Profile

The first link that is checked by TS has to evaluate the route tree and it looks like this with `addChildren` accepting a tuple

![image](https://github.com/TanStack/router/assets/7883601/d3fff175-c516-45e8-b95f-f305a033a486)

The same node with `addChildren` accepting an object looks like this

![image](https://github.com/TanStack/router/assets/7883601/c602cf6f-7d1b-4e72-bb73-93b8c7396b46)


But thats not the end of the story. Each individual Link is also faster with children as an object. Each Link is checked with the following time when `addChildren` accepts a tuple

![image](https://github.com/TanStack/router/assets/7883601/97527654-4dcd-4bc2-8a74-bdab64e09d7d)

And each Link is checked with the following time with `addChildren` accepts an object

![image](https://github.com/TanStack/router/assets/7883601/7ebd87b1-856c-4593-a2dd-0bd7167b1aab)

As mentioned before. 30ms might not seem like much. But if you have 30ms for every `Link` in your code base, it quickly accumulates. 4ms is a huge difference in these cases

In this PR I have switched file based routing over to using objects automatically for a default performance boost. Code based can opt into the signature.

## Other stuff

### Generating more

File based routing previously has done a lot of string interpretation on the type level in order to calculate things like `fullPath`, `path`, `id` etc. Template literal types are expensive for the compiler and generally in file based routing, these strings can be generated directly by the generator without hurting inference. This speeds up creating routes

### Improve params parsing

A more direct implementation of path params parsing without any additional splitting or mapped types

## What can blow up your TS Performance

**With TSR you want to narrow to the most relevant routes. This means using `from` and `to` on `Link` or other API's**.
 
For example, `<Link search={{ page: 0 }} />` will increase the type checking time on this link because TSR will resolve `search` to all possible routes (a union of all search params) and will therefore spend longer type checking it. This depends on the size of your route tree and how many search params you have but on our large example, I noticed a single `Link` would type check in roughly 100ms.

It is better to narrow the route as much as you can `<Link from={Route.fullPath} search={{ page: 0 }} />` with `from` or `to`. In this case the time type checking `Link` went down to 9ms.

This same logic also applies to `LinkOptions`. If you're using types from TSR which are by default not narrowed to a route then this can also blow up TS performance when merging this props with `Link`. Just remember that when `from` or `to` is not specified then things can start slowing down, so definitely try to do it as little as possible.



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

CI is running for commit 2e08cc8e5c25891bd73334c35c9d4906f08d791e.

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


---

Sent with 💌 from [NxCloud.](https://nx.app?utm_source=pull-request&utm_medium=comment)
chorobin: Still some work needs to be done so not ready just yet
schiller-manuel: we should add that `What can blow up your TS Performance` section into the documentation
chorobin: > we should add that `What can blow up your TS Performance` section into the documentation

Definitely. Think we need to write a section on it so they can go to it and be able to resolve common problems
chorobin: Don't merge yet. Need to double check some things. Ideas 💡
schiller-manuel: @SeanCassiere in light of https://github.com/TanStack/router/pull/1584, can you please review this?
SeanCassiere: Also, we probably want to using the `feat:` or `refactor:` prefix on the commit name. Not sure if we are using conventional commits or angular commits, but if I've not mistaken on one of them using `perf:` triggers a major version bump.
SeanCassiere: > @SeanCassiere in light of #1584, can you please review this?

Trying out the changes from my previous PR on this yielded no collisions that I could observe.

That being said, @chorobin once the merge conflict has been resolved, you'd want to rebase and confirm the tests I introduced in that PR still work.
chorobin: > Also, we probably want to using the `feat:` or `refactor:` prefix on the commit name. Not sure if we are using conventional commits or angular commits, but if I've not mistaken on one of them using `perf:` triggers a major version bump.

I've never heard of this. In conventional commits I thought you had to explicitly mention `!` or `BREAKING CHANGE` to bump major version
schiller-manuel: this can be seen here: https://github.com/TanStack/config/blob/main/src/publish/index.js#L144

however, it only matters that the squashed commit has that prefix, so it can be set when this is merged


Patch:
diff --git a/docs/framework/react/guide/type-safety.md b/docs/framework/react/guide/type-safety.md
index 76d18c19e..c7e6da59e 100644
--- a/docs/framework/react/guide/type-safety.md
+++ b/docs/framework/react/guide/type-safety.md
@@ -110,3 +110,140 @@ const router = createRouter({
   },
 })
 ```
+
+## Performance Recommendations
+
+As your application scales, TypeScript check times will naturally increase. There are a few things to keep in mind when your application scales to keep your TS check times down.
+
+### Narrow to relevant routes as much as you possibly can
+
+Consider the following usage of `Link`
+
+```tsx
+<Link to=".." search={{ page: 0 }} />
+<Link to="." search={{ page: 0 }} />
+<Link search={{ page: 0 }} />
+```
+
+**These examples are bad for TS performance**. That's because `search` resolves to a union of all `search` params for all routes and TS has to check whatever you pass to the `search` prop against this potentially big union. As your application grows, this check time will increase linearly to number of routes and search params. We have done our best to optimise for this case (TypeScript will typically do this work once and cache it) but the initial check against this large union is expensive. This also applies to `params` and other API's such as `useSearch`, `useParams`, `useNavigate` etc
+
+Instead you should try to narrow to relevant routes with `from` or `to`
+
+```tsx
+<Link from={Route.fullPath} to=".." search={{page: 0}} />
+<Link from="/posts" to=".." search={{page: 0}} />
+```
+
+Remember you can always pass a union to `to` or `from` to narrow the routes you're interested in.
+
+```tsx
+const from: '/posts/$postId/deep' | '/posts/' = '/posts/'
+<Link from={from} to='..' />
+```
+
+You can also pass branches to `from` to only resolve `search` or `params` to be from any descendants of that branch
+
+```tsx
+const from = '/posts'
+<Link from={from} to='..' />
+```
+
+`/posts` could be a branch with many descendants which share the same `search` or `params`
+
+### Consider using the object syntax of `addChildren`
+
+It's tyipcal of routes to have `params` `search`, `loaders` or `context` that can even reference external dependencies which are also heavy on TS inference. For such applications, using objects for creating the route tree can be more performant than tuples.
+
+`createChildren` also can accept an object. For large route trees with complex routes and external libraries, objects can be much faster for TS to type check as opposed to large tuples. The performance gains depend on your project, what external dependencies you have and how the types for those libraries are written
+
+```tsx
+const routeTree = rootRoute.addChildren({
+  postsRoute: postsRoute.addChildren({ postRoute, postsIndexRoute }),
+  indexRoute,
+})
+```
+
+Note this syntax is more verbose but has better TS performance. With file based routing, the route tree is generated for you so a verbose route tree is not a concern
+
+### Avoid internal types without narrowing
+
+It's common you might want to re-use types exposed. For example you might be tempted to use `LinkProps` like so
+
+```tsx
+const props: LinkProps = {
+  to: '/posts/',
+}
+
+return (
+  <Link {...props}>
+)
+```
+
+**This is VERY bad for TS Performance**. The problem here is `LinkProps` has no type arguments and is therefore an extremely large type. It includes `search` which is a union of all `search` params, it contains `params` which is a union of all `params`. When merging this object with `Link` it will do a structural comparison of this huge type.
+
+Instead you can use `as const satisfies` to infer a precise type and not `LinkProps` directly to avoid the huge check
+
+```tsx
+const props = {
+  to: '/posts/',
+} as const satisfies LinkProps
+
+return (
+  <Link {...props}>
+)
+```
+
+As `props` is not of type `LinkProps` and therefore this check is cheaper because the type is much more precise. You can also improve type checking further by narrowing `LinkProps`
+
+```tsx
+const props = {
+  to: '/posts/',
+} as const satisfies LinkProps<RegisteredRouter, string '/posts/'>
+
+return (
+  <Link {...props}>
+)
+```
+
+This is even faster as we're checking against the narrowed `LinkProps` type.
+
+You can also use this to narrow the type of `LinkProps` to a specific type to be used as a prop or parameter to a function
+
+```tsx
+export const myLinkProps = [
+  {
+    to: '/posts',
+  },
+  {
+    to: '/posts/$postId',
+    params: { postId: 'postId' },
+  },
+] as const satisfies ReadonlyArray<LinkProps>
+
+export type MyLinkProps = (typeof myLinkProps)[number]
+
+const MyComponent = (props: { linkProps: MyLinkProps }) => {
+  return <Link {...props.linkProps} />
+}
+```
+
+This is faster than using `LinkProps` directly in a component because `MyLinkProps` is a much more precise type
+
+Another solution is not to use `LinkProps` and to provide inversion of control to render a `Link` component narrowed to a specific route. Render props are a good method of inverting control to the user of a component
+
+```tsx
+export interface MyComponentProps {
+  readonly renderLink: () => React.ReactNode
+}
+
+const MyComponent = (props: MyComponentProps) => {
+  return <div>{props.renderLink()}</div>
+}
+
+const Page = () => {
+  return <MyComponent renderLink={() => <Link to="/absolute" />} />
+}
+```
+
+This particular example is very fast as we've inverted control of where we're navigating to the user of the component. The `Link` is narrowed to the exact route
+we want to navigate to
diff --git a/examples/react/basic-file-based/src/routeTree.gen.ts b/examples/react/basic-file-based/src/routeTree.gen.ts
index 19f480335..7ae8fadcb 100644
--- a/examples/react/basic-file-based/src/routeTree.gen.ts
+++ b/examples/react/basic-file-based/src/routeTree.gen.ts
@@ -73,38 +73,65 @@ const LayoutLayout2LayoutARoute = LayoutLayout2LayoutAImport.update({
 declare module '@tanstack/react-router' {
   interface FileRoutesByPath {
     '/': {
+      id: '/'
+      path: '/'
+      fullPath: '/'
       preLoaderRoute: typeof IndexImport
       parentRoute: typeof rootRoute
     }
     '/_layout': {
+      id: '/_layout'
+      path: ''
+      fullPath: ''
       preLoaderRoute: typeof LayoutImport
       parentRoute: typeof rootRoute
     }
     '/posts': {
+      id: '/posts'
+      path: '/posts'
+      fullPath: '/posts'
       preLoaderRoute: typeof PostsImport
       parentRoute: typeof rootRoute
     }
     '/_layout/_layout-2': {
+      id: '/_layout/_layout-2'
+      path: ''
+      fullPath: ''
       preLoaderRoute: typeof LayoutLayout2Import
       parentRoute: typeof LayoutImport
     }
     '/posts/$postId': {
+      id: '/posts/$postId'
+      path: '/$postId'
+      fullPath: '/posts/$postId'
       preLoaderRoute: typeof PostsPostIdImport
       parentRoute: typeof PostsImport
     }
     '/posts/': {
+      id: '/posts/'
+      path: '/'
+      fullPath: '/posts/'
       preLoaderRoute: typeof PostsIndexImport
       parentRoute: typeof PostsImport
     }
     '/_layout/_layout-2/layout-a': {
+      id: '/_layout/_layout-2/layout-a'
+      path: '/layout-a'
+      fullPath: '/layout-a'
       preLoaderRoute: typeof LayoutLayout2LayoutAImport
       parentRoute: typeof LayoutLayout2Import
     }
     '/_layout/_layout-2/layout-b': {
+      id: '/_layout/_layout-2/layout-b'
+      path: '/layout-b'
+      fullPath: '/layout-b'
       preLoaderRoute: typeof LayoutLayout2LayoutBImport
       parentRoute: typeof LayoutLayout2Import
     }
     '/posts/$postId/deep': {
+      id: '/posts/$postId/deep'
+      path: '/posts/$postId/deep'
+      fullPath: '/posts/$postId/deep'
       preLoaderRoute: typeof PostsPostIdDeepImport
       parentRoute: typeof rootRoute
     }
@@ -113,16 +140,16 @@ declare module '@tanstack/react-router' {
 
 // Create and export the route tree
 
-export const routeTree = rootRoute.addChildren([
+export const routeTree = rootRoute.addChildren({
   IndexRoute,
-  LayoutRoute.addChildren([
-    LayoutLayout2Route.addChildren([
+  LayoutRoute: LayoutRoute.addChildren({
+    LayoutLayout2Route: LayoutLayout2Route.addChildren({
       LayoutLayout2LayoutARoute,
       LayoutLayout2LayoutBRoute,
-    ]),
-  ]),
-  PostsRoute.addChildren([PostsPostIdRoute, PostsIndexRoute]),
+    }),
+  }),
+  PostsRoute: PostsRoute.addChildren({ PostsPostIdRoute, PostsIndexRoute }),
   PostsPostIdDeepRoute,
-])
+})
 
 /* prettier-ignore-end */
diff --git a/examples/react/large-file-based/package.json b/examples/react/large-file-based/package.json
index 3c8205fb3..f5485e0f9 100644
--- a/examples/react/large-file-based/package.json
+++ b/examples/react/large-file-based/package.json
@@ -11,6 +11,7 @@
     "test:types": "tsc --extendedDiagnostics"
   },
   "dependencies": {
+    "@tanstack/react-query": "^5.17.8",
     "@tanstack/react-router": "^1.32.0",
     "@tanstack/router-devtools": "^1.32.0",
     "@tanstack/router-vite-plugin": "^1.31.18",
diff --git a/examples/react/large-file-based/src/createRoutes.mjs b/examples/react/large-file-based/src/createRoutes.mjs
index 8ad1b6895..c86294b69 100644
--- a/examples/react/large-file-based/src/createRoutes.mjs
+++ b/examples/react/large-file-based/src/createRoutes.mjs
@@ -1,21 +1,48 @@
 import { readFile, writeFile, mkdir } from 'fs/promises'
 import { existsSync } from 'fs'
 
-const length = 200
+const length = 100
 
 const main = async () => {
   const absolute = (await readFile('./src/routes/absolute.tsx')).toString()
   const relative = (await readFile('./src/routes/relative.tsx')).toString()
+  const searchRoute = (
+    await readFile('./src/routes/search/route.tsx')
+  ).toString()
+  const search = (
+    await readFile('./src/routes/search/searchPlaceholder.tsx')
+  ).toString()
+  const paramsRoute = (
+    await readFile('./src/routes/params/route.tsx')
+  ).toString()
+  const params = await (
+    await readFile('./src/routes/params/$paramsPlaceholder.tsx')
+  ).toString()
 
   if (!existsSync('./src/routes/(gen)')) {
     await mkdir('./src/routes/(gen)')
   }
 
+  if (!existsSync('./src/routes/(gen)/search')) {
+    await mkdir('./src/routes/(gen)/search')
+  }
+
+  if (!existsSync('./src/routes/(gen)/params')) {
+    await mkdir('./src/routes/(gen)/params')
+  }
+
+  await writeFile('./src/routes/(gen)/search/route.tsx', searchRoute)
+  await writeFile('./src/routes/(gen)/params/route.tsx', paramsRoute)
+
   for (let y = 0; y < length; y = y + 1) {
     const replacedAbsolute = absolute.replaceAll('/absolute', `/absolute${y}`)
     const replacedRelative = relative.replaceAll('/relative', `/relative${y}`)
+    const replacedSearch = search.replaceAll('searchPlaceholder', `search${y}`)
+    const replacedParams = params.replaceAll('paramsPlaceholder', `param${y}`)
     await writeFile(`./src/routes/(gen)/absolute${y}.tsx`, replacedAbsolute)
     await writeFile(`./src/routes/(gen)/relative${y}.tsx`, replacedRelative)
+    await writeFile(`./src/routes/(gen)/search/search${y}.tsx`, replacedSearch)
+    await writeFile(`./src/routes/(gen)/params/$param${y}.tsx`, replacedParams)
   }
 }
 
diff --git a/examples/react/large-file-based/src/main.tsx b/examples/react/large-file-based/src/main.tsx
index 489ebafac..d0ffa034b 100644
--- a/examples/react/large-file-based/src/main.tsx
+++ b/examples/react/large-file-based/src/main.tsx
@@ -1,12 +1,18 @@
 import React from 'react'
 import ReactDOM from 'react-dom/client'
 import { RouterProvider, createRouter } from '@tanstack/react-router'
+import { QueryClient } from '@tanstack/react-query'
 import { routeTree } from './routeTree.gen'
 
+export const queryClient = new QueryClient()
+
 // Set up a Router instance
 const router = createRouter({
   routeTree,
   defaultPreload: 'intent',
+  context: {
+    queryClient,
+  },
 })
 
 // Register things for typesafety
diff --git a/examples/react/large-file-based/src/routeTree.gen.ts b/examples/react/large-file-based/src/routeTree.gen.ts
index 307537052..7d438ba48 100644
--- a/examples/react/large-file-based/src/routeTree.gen.ts
+++ b/examples/react/large-file-based/src/routeTree.gen.ts
@@ -12,8 +12,13 @@
 
 import { Route as rootRoute } from './routes/__root'
 import { Route as RelativeImport } from './routes/relative'
+import { Route as LinkPropsImport } from './routes/linkProps'
 import { Route as AbsoluteImport } from './routes/absolute'
+import { Route as SearchRouteImport } from './routes/search/route'
+import { Route as ParamsRouteImport } from './routes/params/route'
 import { Route as IndexImport } from './routes/index'
+import { Route as SearchSearchPlaceholderImport } from './routes/search/searchPlaceholder'
+import { Route as ParamsParamsPlaceholderImport } from './routes/params/$paramsPlaceholder'
 
 // Create/Update Routes
 
@@ -22,41 +27,117 @@ const RelativeRoute = RelativeImport.update({
   getParentRoute: () => rootRoute,
 } as any)
 
+const LinkPropsRoute = LinkPropsImport.update({
+  path: '/linkProps',
+  getParentRoute: () => rootRoute,
+} as any)
+
 const AbsoluteRoute = AbsoluteImport.update({
   path: '/absolute',
   getParentRoute: () => rootRoute,
 } as any)
 
+const SearchRouteRoute = SearchRouteImport.update({
+  path: '/search',
+  getParentRoute: () => rootRoute,
+} as any)
+
+const ParamsRouteRoute = ParamsRouteImport.update({
+  path: '/params',
+  getParentRoute: () => rootRoute,
+} as any)
+
 const IndexRoute = IndexImport.update({
   path: '/',
   getParentRoute: () => rootRoute,
 } as any)
 
+const SearchSearchPlaceholderRoute = SearchSearchPlaceholderImport.update({
+  path: '/searchPlaceholder',
+  getParentRoute: () => SearchRouteRoute,
+} as any)
+
+const ParamsParamsPlaceholderRoute = ParamsParamsPlaceholderImport.update({
+  path: '/$paramsPlaceholder',
+  getParentRoute: () => ParamsRouteRoute,
+} as any)
+
 // Populate the FileRoutesByPath interface
 
 declare module '@tanstack/react-router' {
   interface FileRoutesByPath {
     '/': {
+      id: '/'
+      path: '/'
+      fullPath: '/'
       preLoaderRoute: typeof IndexImport
       parentRoute: typeof rootRoute
     }
+    '/params': {
+      id: '/params'
+      path: '/params'
+      fullPath: '/params'
+      preLoaderRoute: typeof ParamsRouteImport
+      parentRoute: typeof rootRoute
+    }
+    '/search': {
+      id: '/search'
+      path: '/search'
+      fullPath: '/search'
+      preLoaderRoute: typeof SearchRouteImport
+      parentRoute: typeof rootRoute
+    }
     '/absolute': {
+      id: '/absolute'
+      path: '/absolute'
+      fullPath: '/absolute'
       preLoaderRoute: typeof AbsoluteImport
       parentRoute: typeof rootRoute
     }
+    '/linkProps': {
+      id: '/linkProps'
+      path: '/linkProps'
+      fullPath: '/linkProps'
+      preLoaderRoute: typeof LinkPropsImport
+      parentRoute: typeof rootRoute
+    }
     '/relative': {
+      id: '/relative'
+      path: '/relative'
+      fullPath: '/relative'
       preLoaderRoute: typeof RelativeImport
       parentRoute: typeof rootRoute
     }
+    '/params/$paramsPlaceholder': {
+      id: '/params/$paramsPlaceholder'
+      path: '/$paramsPlaceholder'
+      fullPath: '/params/$paramsPlaceholder'
+      preLoaderRoute: typeof ParamsParamsPlaceholderImport
+      parentRoute: typeof ParamsRouteImport
+    }
+    '/search/searchPlaceholder': {
+      id: '/search/searchPlaceholder'
+      path: '/searchPlaceholder'
+      fullPath: '/search/searchPlaceholder'
+      preLoaderRoute: typeof SearchSearchPlaceholderImport
+      parentRoute: typeof SearchRouteImport
+    }
   }
 }
 
 // Create and export the route tree
 
-export const routeTree = rootRoute.addChildren([
+export const routeTree = rootRoute.addChildren({
   IndexRoute,
+  ParamsRouteRoute: ParamsRouteRoute.addChildren({
+    ParamsParamsPlaceholderRoute,
+  }),
+  SearchRouteRoute: SearchRouteRoute.addChildren({
+    SearchSearchPlaceholderRoute,
+  }),
   AbsoluteRoute,
+  LinkPropsRoute,
   RelativeRoute,
-])
+})
 
 /* prettier-ignore-end */
diff --git a/examples/react/large-file-based/src/routes/__root.tsx b/examples/react/large-file-based/src/routes/__root.tsx
index 4ee442acf..cbe933d18 100644
--- a/examples/react/large-file-based/src/routes/__root.tsx
+++ b/examples/react/large-file-based/src/routes/__root.tsx
@@ -1,13 +1,20 @@
 import * as React from 'react'
-import { Link, Outlet, createRootRoute } from '@tanstack/react-router'
+import {
+  Link,
+  Outlet,
+  createRootRouteWithContext,
+} from '@tanstack/react-router'
+import { type QueryClient } from '@tanstack/react-query'
 import { TanStackRouterDevtools } from '@tanstack/router-devtools'
 
-export const Route = createRootRoute({
-  component: RootComponent,
-  notFoundComponent: () => {
-    return <p>Not Found (on root route)</p>
+export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()(
+  {
+    component: RootComponent,
+    notFoundComponent: () => {
+      return <p>Not Found (on root route)</p>
+    },
   },
-})
+)
 
 function RootComponent() {
   return (
diff --git a/examples/react/large-file-based/src/routes/linkProps.tsx b/examples/react/large-file-based/src/routes/linkProps.tsx
new file mode 100644
index 000000000..693fbe0a9
--- /dev/null
+++ b/examples/react/large-file-based/src/routes/linkProps.tsx
@@ -0,0 +1,14 @@
+import * as React from 'react'
+import { Link, type LinkProps, createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/linkProps')({
+  component: LinkPropsPage,
+})
+
+function LinkPropsPage() {
+  const linkProps: LinkProps = {
+    to: '/absolute',
+  }
+
+  return <Link {...linkProps} />
+}
diff --git a/examples/react/large-file-based/src/routes/params/$paramsPlaceholder.tsx b/examples/react/large-file-based/src/routes/params/$paramsPlaceholder.tsx
new file mode 100644
index 000000000..35f85eab9
--- /dev/null
+++ b/examples/react/large-file-based/src/routes/params/$paramsPlaceholder.tsx
@@ -0,0 +1,40 @@
+import * as React from 'react'
+import { Link, createFileRoute } from '@tanstack/react-router'
+import { z } from 'zod'
+import { queryOptions } from '@tanstack/react-query'
+
+const params = z.object({
+  oneParamsPlaceholder: z.literal('oneParamsPlaceholder'),
+  twoParamsPlaceholder: z.literal('twoParamsPlaceholder'),
+  threeParamsPlaceholder: z.literal('threeParamsPlaceholder'),
+})
+
+const loaderResult = z.object({
+  params,
+})
+
+const paramsQueryOptions = queryOptions({
+  queryKey: ['paramsPlaceholder'],
+  queryFn: () => {
+    return loaderResult.parse({})
+  },
+})
+
+export const Route = createFileRoute('/params/$paramsPlaceholder')({
+  component: ParamsComponent,
+  loader: (opts) =>
+    opts.context.queryClient.ensureQueryData(paramsQueryOptions),
+})
+
+function ParamsComponent() {
+  return (
+    <div className="p-2 space-y-2">
+      <Link
+        to="/params/$paramsPlaceholder"
+        params={{
+          paramsPlaceholder: 'params',
+        }}
+      />
+    </div>
+  )
+}
diff --git a/examples/react/large-file-based/src/routes/params/route.tsx b/examples/react/large-file-based/src/routes/params/route.tsx
new file mode 100644
index 000000000..32f2b8202
--- /dev/null
+++ b/examples/react/large-file-based/src/routes/params/route.tsx
@@ -0,0 +1,6 @@
+import * as React from 'react'
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/params')({
+  component: () => <div>Hello /params!</div>,
+})
diff --git a/examples/react/large-file-based/src/routes/search/route.tsx b/examples/react/large-file-based/src/routes/search/route.tsx
new file mode 100644
index 000000000..6155a0d33
--- /dev/null
+++ b/examples/react/large-file-based/src/routes/search/route.tsx
@@ -0,0 +1,6 @@
+import * as React from 'react'
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/search')({
+  component: () => <div>Hello /search!</div>,
+})
diff --git a/examples/react/large-file-based/src/routes/search/searchPlaceholder.tsx b/examples/react/large-file-based/src/routes/search/searchPlaceholder.tsx
new file mode 100644
index 000000000..c43b546e9
--- /dev/null
+++ b/examples/react/large-file-based/src/routes/search/searchPlaceholder.tsx
@@ -0,0 +1,53 @@
+import * as React from 'react'
+import { Link, createFileRoute } from '@tanstack/react-router'
+
+import { z } from 'zod'
+import { queryOptions } from '@tanstack/react-query'
+
+const search = z.object({
+  searchPlaceholder: z.literal('searchPlaceholder'),
+  page: z.number(),
+  offset: z.number(),
+  search: z.string(),
+})
+
+const loaderResult = z.object({
+  searchPlaceholder: z.number(),
+})
+
+const searchQueryOptions = queryOptions({
+  queryKey: ['searchPlaceholder'],
+  queryFn: () => {
+    const result = loaderResult.parse({
+      searchPlaceholder: 0,
+    })
+
+    return result
+  },
+})
+
+export const Route = createFileRoute('/search/searchPlaceholder')({
+  component: SearchComponent,
+  validateSearch: search,
+  loader: (opts) =>
+    opts.context.queryClient.ensureQueryData(searchQueryOptions),
+})
+
+function SearchComponent() {
+  return (
+    <div className="p-2 space-y-2">
+      <Link
+        to="/search/searchPlaceholder"
+        className="block py-1 text-blue-800 hover:text-blue-600"
+        search={{
+          searchPlaceholder: 'searchPlaceholder',
+          page: 0,
+          offset: 10,
+          search: 'search',
+        }}
+      >
+        Search
+      </Link>
+    </div>
+  )
+}
diff --git a/packages/react-router/src/fileRoute.ts b/packages/react-router/src/fileRoute.ts
index bfcd910c3..362211d3d 100644
--- a/packages/react-router/src/fileRoute.ts
+++ b/packages/react-router/src/fileRoute.ts
@@ -11,10 +11,13 @@ import type {
   AnyRoute,
   AnySearchSchema,
   FileBaseRouteOptions,
-  MergeFromFromParent,
-  ResolveFullPath,
+  ResolveAllContext,
+  ResolveAllParamsFromParent,
   ResolveFullSearchSchema,
   ResolveFullSearchSchemaInput,
+  ResolveLoaderData,
+  ResolveRouteContext,
+  ResolveSearchSchemaUsed,
   RootRouteId,
   Route,
   RouteConstraints,
@@ -36,79 +39,13 @@ export interface FileRoutesByPath {
   // }
 }
 
-type Replace<
-  TValue extends string,
-  TFrom extends string,
-  TTo extends string,
-  TAcc extends string = '',
-> = TValue extends `${infer Start}${TFrom}${infer Rest}`
-  ? Replace<Rest, TFrom, TTo, `${TAcc}${Start}${TTo}`>
-  : `${TAcc}${TValue}`
-
-export type TrimLeft<
-  TValue extends string,
-  TStartsWith extends string,
-> = TValue extends `${TStartsWith}${infer U}` ? U : TValue
-
-export type TrimRight<
-  TValue extends string,
-  TEndsWith extends string,
-> = TValue extends `${infer U}${TEndsWith}` ? U : TValue
-
-export type Trim<TValue extends string, TFind extends string> = TrimLeft<
-  TrimRight<TValue, TFind>,
-  TFind
->
-
-export type RemoveUnderScores<T extends string> = Replace<
-  Replace<TrimRight<TrimLeft<T, '/_'>, '_'>, '_/', '/'>,
-  '/_',
-  '/'
->
-
-type RemoveRouteGroups<T extends string> =
-  T extends `${infer Before}(${string})${infer After}`
-    ? RemoveRouteGroups<`${Before}${After}`>
-    : T
-
-type NormalizeSlashes<T extends string> =
-  T extends `${infer Before}//${infer After}`
-    ? NormalizeSlashes<`${Before}/${After}`>
-    : T
-
-export type ResolveFilePath<
-  TParentRoute extends AnyRoute,
-  TFilePath extends string,
-> = TParentRoute['id'] extends RootRouteId
-  ? TrimPathLeft<TFilePath>
-  : TFilePath extends `${TParentRoute['types']['customId']}${infer TRest}`
-    ? TRest
-    : TFilePath
-
-export type FileRoutePath<
-  TParentRoute extends AnyRoute,
-  TFilePath extends string,
-  TResolvedFilePath = ResolveFilePath<TParentRoute, TFilePath>,
-> = TResolvedFilePath extends `_${string}`
-  ? ''
-  : TResolvedFilePath extends `/_${string}`
-    ? ''
-    : TResolvedFilePath
-
 export function createFileRoute<
   TFilePath extends keyof FileRoutesByPath,
   TParentRoute extends AnyRoute = FileRoutesByPath[TFilePath]['parentRoute'],
-  TId extends RouteConstraints['TId'] = NormalizeSlashes<
-    RemoveRouteGroups<TFilePath>
-  >,
-  TPath extends RouteConstraints['TPath'] = FileRoutePath<
-    TParentRoute,
-    TFilePath
-  >,
-  TFullPath extends RouteConstraints['TFullPath'] = ResolveFullPath<
-    TParentRoute,
-    NormalizeSlashes<RemoveRouteGroups<RemoveUnderScores<TPath>>>
-  >,
+  TId extends RouteConstraints['TId'] = FileRoutesByPath[TFilePath]['id'],
+  TPath extends RouteConstraints['TPath'] = FileRoutesByPath[TFilePath]['path'],
+  TFullPath extends
+    RouteConstraints['TFullPath'] = FileRoutesByPath[TFilePath]['fullPath'],
 >(path: TFilePath) {
   return new FileRoute<TFilePath, TParentRoute, TId, TPath, TFullPath>(path, {
     silent: true,
@@ -122,15 +59,10 @@ export function createFileRoute<
 export class FileRoute<
   TFilePath extends keyof FileRoutesByPath,
   TParentRoute extends AnyRoute = FileRoutesByPath[TFilePath]['parentRoute'],
-  TId extends RouteConstraints['TId'] = TFilePath,
-  TPath extends RouteConstraints['TPath'] = FileRoutePath<
-    TParentRoute,
-    TFilePath
-  >,
-  TFullPath extends RouteConstraints['TFullPath'] = ResolveFullPath<
-    TParentRoute,
-    RemoveUnderScores<TPath>
-  >,
+  TId extends RouteConstraints['TId'] = FileRoutesByPath[TFilePath]['id'],
+  TPath extends RouteConstraints['TPath'] = FileRoutesByPath[TFilePath]['path'],
+  TFullPath extends
+    RouteConstraints['TFullPath'] = FileRoutesByPath[TFilePath]['fullPath'],
 > {
   silent?: boolean
 
@@ -142,37 +74,27 @@ export class FileRoute<
   }
 
   createRoute = <
-    TSearchSchemaInput extends RouteConstraints['TSearchSchema'] = {},
-    TSearchSchema extends RouteConstraints['TSearchSchema'] = {},
-    TSearchSchemaUsed = TSearchSchemaInput extends SearchSchemaInput
-      ? Omit<TSearchSchemaInput, keyof SearchSchemaInput>
-      : TSearchSchema,
+    TSearchSchemaInput = {},
+    TSearchSchema = {},
+    TSearchSchemaUsed = ResolveSearchSchemaUsed<
+      TSearchSchemaInput,
+      TSearchSchema
+    >,
     TFullSearchSchemaInput = ResolveFullSearchSchemaInput<
       TParentRoute,
       TSearchSchemaUsed
     >,
     TFullSearchSchema = ResolveFullSearchSchema<TParentRoute, TSearchSchema>,
     TParams = Record<ParsePathParams<TPath>, string>,
-    TAllParams = MergeFromFromParent<
-      TParentRoute['types']['allParams'],
-      TParams
-    >,
-    TRouteContextReturn extends
-      RouteConstraints['TRouteContext'] = RouteContext,
-    TRouteContext = [TRouteContextReturn] extends [never]
-      ? RouteContext
-      : TRouteContextReturn,
-    TAllContext = Assign<
-      IsAny<TParentRoute['types']['allContext'], {}>,
-      TRouteContext
-    >,
-    TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
+    TAllParams = ResolveAllParamsFromParent<TParentRoute, TParams>,
+    TRouteContextReturn = RouteContext,
+    TRouteContext = ResolveRouteContext<TRouteContextReturn>,
+    TAllContext = ResolveAllContext<TParentRoute, TRouteContext>,
+    TRouterContext = AnyContext,
     TLoaderDeps extends Record<string, any> = {},
     TLoaderDataReturn = unknown,
-    TLoaderData = [TLoaderDataReturn] extends [never]
-      ? undefined
-      : TLoaderDataReturn,
-    TChildren extends RouteConstraints['TChildren'] = unknown,
+    TLoaderData = ResolveLoaderData<TLoaderDataReturn>,
+    TChildren = unknown,
   >(
     options?: FileBaseRouteOptions<
       TParentRoute,
diff --git a/packages/react-router/src/index.tsx b/packages/react-router/src/index.tsx
index 0257eebee..0b079ed6d 100644
--- a/packages/react-router/src/index.tsx
+++ b/packages/react-router/src/index.tsx
@@ -28,12 +28,6 @@ export {
   createLazyRoute,
   createLazyFileRoute,
   type FileRoutesByPath,
-  type Trim,
-  type TrimLeft,
-  type TrimRight,
-  type RemoveUnderScores,
-  type ResolveFilePath,
-  type FileRoutePath,
   type LazyRouteOptions,
 } from './fileRoute'
 export * from './history'
@@ -60,7 +54,6 @@ export {
   type ToMaskOptions,
   type ToSubOptions,
   type ResolveRoute,
-  type ParamOptions,
   type SearchParamOptions,
   type PathParamOptions,
   type ToPathOption,
@@ -162,7 +155,6 @@ export {
   type ResolveFullSearchSchema,
   type ResolveFullSearchSchemaInput,
   type AnyRoute,
-  type MergeFromFromParent,
   type ResolveAllParams,
   type RouteConstraints,
   type AnyRootRoute,
diff --git a/packages/react-router/src/link.tsx b/packages/react-router/src/link.tsx
index e95e67561..2f4fb184a 100644
--- a/packages/react-router/src/link.tsx
+++ b/packages/react-router/src/link.tsx
@@ -6,9 +6,11 @@ import { useRouter } from './useRouter'
 import { deepEqual, exactPathTest, functionalUpdate } from './utils'
 import type { AnyRouter, ParsedLocation } from '.'
 import type { HistoryState } from '@tanstack/history'
-import type { Trim } from './fileRoute'
 import type { AnyRoute, RootSearchSchema } from './route'
 import type {
+  CatchAllPaths,
+  ParseRoute,
+  ParseRouteWithoutBranches,
   RouteByPath,
   RouteByToPath,
   RoutePaths,
@@ -56,13 +58,16 @@ export type Split<TValue, TIncludeTrailingSlash = true> = TValue extends unknown
       : never
   : never
 
-export type ParsePathParams<T extends string> = keyof {
-  [K in Trim<Split<T>[number], '_'> as K extends `$${infer L}`
-    ? L extends ''
+export type ParsePathParams<
+  T extends string,
+  TAcc = never,
+> = T extends `${string}$${infer TPossiblyParam}`
+  ? TPossiblyParam extends `${infer TParam}/${infer TRest}`
+    ? ParsePathParams<TRest, TParam extends '' ? '_splat' : TParam | TAcc>
+    : TPossiblyParam extends ''
       ? '_splat'
-      : L
-    : never]: K
-}
+      : TPossiblyParam | TAcc
+  : TAcc
 
 export type Join<T, TDelimiter extends string = '/'> = T extends []
   ? ''
@@ -168,7 +173,9 @@ export type NavigateOptions<
   TTo extends string = '',
   TMaskFrom extends RoutePaths<TRouter['routeTree']> | string = TFrom,
   TMaskTo extends string = '',
-> = ToOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> & {
+> = ToOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> & NavigateOptionProps
+
+export interface NavigateOptionProps {
   // `replace` is a boolean that determines whether the navigation should replace the current history entry or push a new one.
   replace?: boolean
   resetScroll?: boolean
@@ -184,7 +191,13 @@ export type ToOptions<
   TTo extends string = '',
   TMaskFrom extends RoutePaths<TRouter['routeTree']> | string = TFrom,
   TMaskTo extends string = '',
-> = ToSubOptions<TRouter, TFrom, TTo> & {
+> = ToSubOptions<TRouter, TFrom, TTo> & MaskOptions<TRouter, TMaskFrom, TMaskTo>
+
+export interface MaskOptions<
+  in out TRouter extends AnyRouter,
+  in out TMaskFrom extends RoutePaths<TRouter['routeTree']> | string,
+  in out TMaskTo extends string,
+> {
   _fromLocation?: ParsedLocation
   mask?: ToMaskOptions<TRouter, TMaskFrom, TMaskTo>
 }
@@ -201,67 +214,95 @@ export type ToSubOptions<
   TRouter extends AnyRouter = RegisteredRouter,
   TFrom extends RoutePaths<TRouter['routeTree']> | string = string,
   TTo extends string = '',
-> = {
+> = ToSubOptionsProps<TRouter, TFrom, TTo> &
+  SearchParamOptions<TRouter, TFrom, TTo> &
+  PathParamOptions<TRouter, TFrom, TTo>
+
+export interface ToSubOptionsProps<
+  in out TRouter extends AnyRouter = RegisteredRouter,
+  in out TFrom extends RoutePaths<TRouter['routeTree']> | string = string,
+  in out TTo extends string = '',
+> {
   to?: ToPathOption<TRouter, 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<TRouter['routeTree'], TFrom> & {}
-  // // When using relative route paths, this option forces resolution from the current path, instead of the route API's path or `from` path
-} & SearchParamOptions<TRouter, TFrom, TTo> &
-  PathParamOptions<TRouter, TFrom, TTo>
+  from?: RoutePathsAutoComplete<TRouter, TFrom> & {}
+}
 
-type ParamsReducer<TFrom, TTo> = TTo | ((current: TFrom) => TTo)
+export type ParamsReducerFn<
+  in out TRouter extends AnyRouter,
+  in out TParamVariant extends ParamVariant,
+  in out TFrom,
+  in out TTo,
+> = (
+  current: Expand<ResolveFromParams<TRouter, TParamVariant, TFrom>>,
+) => Expand<ResolveRelativeToParams<TRouter, TParamVariant, TFrom, TTo>>
+
+type ParamsReducer<
+  TRouter extends AnyRouter,
+  TParamVariant extends ParamVariant,
+  TFrom,
+  TTo,
+> =
+  | Expand<ResolveRelativeToParams<TRouter, TParamVariant, TFrom, TTo>>
+  | (ParamsReducerFn<TRouter, TParamVariant, TFrom, TTo> & {})
 
 type ParamVariant = 'PATH' | 'SEARCH'
 
-type ExcludeRootSearchSchema<T, TExcluded = Exclude<T, RootSearchSchema>> = [
-  TExcluded,
-] extends [never]
-  ? {}
-  : TExcluded
+type ExcludeRootSearchSchema<T> =
+  Exclude<T, RootSearchSchema> extends never ? {} : Exclude<T, RootSearchSchema>
 
 export type ResolveRoute<
   TRouter extends AnyRouter,
   TFrom,
   TTo,
-  TPath = string extends TFrom
-    ? TTo
-    : string extends TTo
-      ? TFrom
-      : ResolveRelativePath<TFrom, TTo>,
-> = TPath extends string
-  ? string extends TTo
-    ? RouteByPath<TRouter['routeTree'], TPath>
-    : RouteByToPath<TRouter, TPath>
-  : never
+  TPath = ResolveRelativePath<TFrom, TTo>,
+> = string extends TPath
+  ? ParseRouteWithoutBranches<TRouter['routeTree']>
+  : TPath extends CatchAllPaths
+    ? ParseRouteWithoutBranches<TRouter['routeTree']>
+    : TPath extends string
+      ? string extends TTo
+        ? RouteByPath<TRouter['routeTree'], TPath>
+        : RouteByToPath<TRouter, TPath>
+      : never
 
 type PostProcessParams<
   T,
   TParamVariant extends ParamVariant,
 > = TParamVariant extends 'SEARCH' ? ExcludeRootSearchSchema<T> : T
 
+type ResolveFromParamType<TParamVariant extends ParamVariant> =
+  TParamVariant extends 'PATH' ? 'allParams' : 'fullSearchSchema'
+
 type ResolveFromParams<
   TRouter extends AnyRouter,
   TParamVariant extends ParamVariant,
   TFrom,
-> = PostProcessParams<
-  RouteByPath<TRouter['routeTree'], TFrom>['types'][TParamVariant extends 'PATH'
-    ? 'allParams'
-    : 'fullSearchSchema'],
-  TParamVariant
->
+> = string extends TFrom
+  ? PostProcessParams<
+      ParseRoute<
+        TRouter['routeTree']
+      >['types'][ResolveFromParamType<TParamVariant>],
+      TParamVariant
+    >
+  : RouteByPath<
+      TRouter['routeTree'],
+      TFrom
+    >['types'][ResolveFromParamType<TParamVariant>]
+
+type ResolveToParamType<TParamVariant extends ParamVariant> =
+  TParamVariant extends 'PATH' ? 'allParams' : 'fullSearchSchemaInput'
 
-type ResolveToParams<
+export type ResolveToParams<
   TRouter extends AnyRouter,
   TParamVariant extends ParamVariant,
   TFrom,
   TTo,
   TRoute extends AnyRoute = ResolveRoute<TRouter, TFrom, TTo>,
 > = PostProcessParams<
-  TRoute['types'][TParamVariant extends 'PATH'
-    ? 'allParams'
-    : 'fullSearchSchemaInput'],
+  TRoute['types'][ResolveToParamType<TParamVariant>],
   TParamVariant
 >
 
@@ -280,67 +321,54 @@ type ResolveRelativeToParams<
         TToParams
       >
 
-type MakeOptionalParams<
-  TRouter extends AnyRouter,
-  TParamVariant extends ParamVariant,
-  TFrom,
-  TTo,
-> = TParamVariant extends 'SEARCH'
-  ? {
-      search?:
-        | true
-        | (ParamsReducer<
-            Expand<ResolveFromParams<TRouter, TParamVariant, TFrom>>,
-            Expand<ResolveRelativeToParams<TRouter, TParamVariant, TFrom, TTo>>
-          > & {})
-    }
-  : {
-      params?:
-        | true
-        | (ParamsReducer<
-            Expand<ResolveFromParams<TRouter, TParamVariant, TFrom>>,
-            Expand<ResolveRelativeToParams<TRouter, TParamVariant, TFrom, TTo>>
-          > & {})
-    }
+interface MakeOptionalSearchParams<
+  in out TRouter extends AnyRouter,
+  in out TFrom,
+  in out TTo,
+> {
+  search?: true | (ParamsReducer<TRouter, 'SEARCH', TFrom, TTo> & {})
+}
+
+interface MakeOptionalPathParams<
+  in out TRouter extends AnyRouter,
+  in out TFrom,
+  in out TTo,
+> {
+  params?: true | (ParamsReducer<TRouter, 'PATH', TFrom, TTo> & {})
+}
 
 type MakeRequiredParamsReducer<
   TRouter extends AnyRouter,
   TParamVariant extends ParamVariant,
   TFrom,
-  TToParams,
-  TFromParams = ResolveFromParams<TRouter, TParamVariant, TFrom>,
+  TTo,
 > =
-  | ([TFromParams] extends [WithoutEmpty<PickRequired<TToParams>>]
-      ? true
-      : never)
-  | ParamsReducer<Expand<TFromParams>, TToParams>
+  | (string extends TFrom
+      ? never
+      : ResolveFromParams<TRouter, TParamVariant, TFrom> extends WithoutEmpty<
+            PickRequired<
+              ResolveRelativeToParams<TRouter, TParamVariant, TFrom, TTo>
+            >
+          >
+        ? true
+        : never)
+  | (ParamsReducer<TRouter, TParamVariant, TFrom, TTo> & {})
+
+export interface MakeRequiredPathParams<
+  in out TRouter extends AnyRouter,
+  in out TFrom,
+  in out TTo,
+> {
+  params: MakeRequiredParamsReducer<TRouter, 'PATH', TFrom, TTo> & {}
+}
 
-export type MakeRequiredParams<
-  TRouter extends AnyRouter,
-  TParamVariant extends ParamVariant,
-  TFrom,
-  TTo,
-> = TParamVariant extends 'SEARCH'
-  ? {
-      search: Expand<
-        MakeRequiredParamsReducer<
-          TRouter,
-          TParamVariant,
-          TFrom,
-          Expand<ResolveRelativeToParams<TRouter, TParamVariant, TFrom, TTo>>
-        >
-      > & {}
-    }
-  : {
-      params: Expand<
-        MakeRequiredParamsReducer<
-          TRouter,
-          TParamVariant,
-          TFrom,
-          Expand<ResolveRelativeToParams<TRouter, TParamVariant, TFrom, TTo>>
-        >
-      > & {}
-    }
+export interface MakeRequiredSearchParams<
+  in out TRouter extends AnyRouter,
+  in out TFrom,
+  in out TTo,
+> {
+  search: MakeRequiredParamsReducer<TRouter, 'SEARCH', TFrom, TTo> & {}
+}
 
 export type IsRequiredParams<TParams> = keyof TParams extends infer K extends
   keyof TParams
@@ -356,41 +384,36 @@ export type IsRequired<
   TParamVariant extends ParamVariant,
   TFrom,
   TTo,
-> = string extends TTo
-  ? string extends TFrom
-    ? never
-    : IsRequiredParams<
-        ResolveRelativeToParams<TRouter, TParamVariant, TFrom, TTo>
-      >
-  : IsRequiredParams<
-      ResolveRelativeToParams<TRouter, TParamVariant, TFrom, TTo>
-    >
-
-export type ParamOptions<
-  TRouter extends AnyRouter,
-  TFrom,
-  TTo extends string,
-  TParamVariant extends ParamVariant,
 > =
-  IsRequired<TRouter, TParamVariant, TFrom, TTo> extends never
-    ? MakeOptionalParams<TRouter, TParamVariant, TFrom, TTo>
-    : MakeRequiredParams<TRouter, TParamVariant, TFrom, TTo>
+  string extends ResolveRelativePath<TFrom, TTo>
+    ? never
+    : ResolveRelativePath<TFrom, TTo> extends CatchAllPaths
+      ? never
+      : IsRequiredParams<
+          ResolveRelativeToParams<TRouter, TParamVariant, TFrom, TTo>
+        >
 
 export type SearchParamOptions<
   TRouter extends AnyRouter,
   TFrom,
   TTo extends string,
-> = ParamOptions<TRouter, TFrom, TTo, 'SEARCH'>
+> =
+  IsRequired<TRouter, 'SEARCH', TFrom, TTo> extends never
+    ? MakeOptionalSearchParams<TRouter, TFrom, TTo>
+    : MakeRequiredSearchParams<TRouter, TFrom, TTo>
 
 export type PathParamOptions<
   TRouter extends AnyRouter,
   TFrom,
   TTo extends string,
-> = ParamOptions<TRouter, TFrom, TTo, 'PATH'>
+> =
+  IsRequired<TRouter, 'PATH', TFrom, TTo> extends never
+    ? MakeOptionalPathParams<TRouter, TFrom, TTo>
+    : MakeRequiredPathParams<TRouter, TFrom, TTo>
 
 export type ToPathOption<
   TRouter extends AnyRouter = AnyRouter,
-  TFrom extends RoutePaths<TRouter['routeTree']> | string = string,
+  TFrom extends string = string,
   TTo extends string = string,
 > =
   | CheckPath<TRouter, TTo, never, TFrom, TTo>
@@ -408,11 +431,13 @@ export interface ActiveOptions {
 
 export type LinkOptions<
   TRouter extends AnyRouter = RegisteredRouter,
-  TFrom extends RoutePaths<TRouter['routeTree']> | string = string,
+  TFrom extends string = string,
   TTo extends string = '',
-  TMaskFrom extends RoutePaths<TRouter['routeTree']> | string = TFrom,
+  TMaskFrom extends string = TFrom,
   TMaskTo extends string = '',
-> = NavigateOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> & {
+> = NavigateOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> & LinkOptionsProps
+
+export interface LinkOptionsProps {
   // The standard anchor tag target attribute
   target?: HTMLAnchorElement['target']
   // Defaults to `{ exact: false, includeHash: false }`
@@ -426,31 +451,41 @@ export type LinkOptions<
 }
 
 export type CheckPath<TRouter extends AnyRouter, TPass, TFail, TFrom, TTo> =
-  ResolveRoute<TRouter, TFrom, TTo> extends never ? TFail : TPass
-
-export type ResolveRelativePath<TFrom, TTo = '.'> = TFrom extends string
-  ? TTo extends string
-    ? TTo extends '.'
-      ? TFrom
-      : TTo extends `./`
-        ? Join<[TFrom, '/']>
-        : TTo extends `./${infer TRest}`
-          ? ResolveRelativePath<TFrom, TRest>
-          : TTo extends `/${infer TRest}`
-            ? TTo
-            : Split<TTo> extends ['..', ...infer ToRest]
-              ? Split<TFrom> extends [...infer FromRest, infer FromTail]
-                ? ToRest extends ['/']
-                  ? Join<['/', ...FromRest, '/']>
-                  : ResolveRelativePath<Join<FromRest>, Join<ToRest>>
-                : never
-              : Split<TTo> extends ['.', ...infer ToRest]
-                ? ToRest extends ['/']
-                  ? Join<[TFrom, '/']>
-                  : ResolveRelativePath<TFrom, Join<ToRest>>
-                : CleanPath<Join<['/', ...Split<TFrom>, ...Split<TTo>]>>
-    : never
-  : never
+  string extends ResolveRelativePath<TFrom, TTo>
+    ? TPass
+    : ResolveRelativePath<TFrom, TTo> extends CatchAllPaths
+      ? TPass
+      : ResolveRoute<TRouter, TFrom, TTo> extends never
+        ? TFail
+        : TPass
+
+export type ResolveRelativePath<TFrom, TTo = '.'> = string extends TFrom
+  ? TTo
+  : string extends TTo
+    ? TFrom
+    : TFrom extends string
+      ? TTo extends string
+        ? TTo extends '.'
+          ? TFrom
+          : TTo extends `./`
+            ? Join<[TFrom, '/']>
+            : TTo extends `./${infer TRest}`
+              ? ResolveRelativePath<TFrom, TRest>
+              : TTo extends `/${infer TRest}`
+                ? TTo
+                : Split<TTo> extends ['..', ...infer ToRest]
+                  ? Split<TFrom> extends [...infer FromRest, infer FromTail]
+                    ? ToRest extends ['/']
+                      ? Join<['/', ...FromRest, '/']>
+                      : ResolveRelativePath<Join<FromRest>, Join<ToRest>>
+                    : never
+                  : Split<TTo> extends ['.', ...infer ToRest]
+                    ? ToRest extends ['/']
+                      ? Join<[TFrom, '/']>
+                      : ResolveRelativePath<TFrom, Join<ToRest>>
+                    : CleanPath<Join<['/', ...Split<TFrom>, ...Split<TTo>]>>
+        : never
+      : never
 
 // type Test1 = ResolveRelativePath<'/', '/posts'>
 // //   ^?
@@ -735,11 +770,13 @@ export type UseLinkPropsOptions<
 
 export type ActiveLinkOptions<
   TRouter extends AnyRouter = RegisteredRouter,
-  TFrom extends RoutePaths<TRouter['routeTree']> | string = string,
+  TFrom extends string = string,
   TTo extends string = '',
-  TMaskFrom extends RoutePaths<TRouter['routeTree']> | string = TFrom,
+  TMaskFrom extends string = TFrom,
   TMaskTo extends string = '',
-> = LinkOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> & {
+> = LinkOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> & ActiveLinkOptionProps
+
+export interface ActiveLinkOptionProps {
   // A function that returns additional props for the `active` state of this link. These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated)
   activeProps?:
     | React.AnchorHTMLAttributes<HTMLAnchorElement>
@@ -752,11 +789,14 @@ export type ActiveLinkOptions<
 
 export type LinkProps<
   TRouter extends AnyRouter = RegisteredRouter,
-  TFrom extends RoutePaths<TRouter['routeTree']> | string = string,
-  TTo extends string = string,
-  TMaskFrom extends RoutePaths<TRouter['routeTree']> | string = TFrom,
+  TFrom extends string = string,
+  TTo extends string = '',
+  TMaskFrom extends string = TFrom,
   TMaskTo extends string = '',
-> = ActiveLinkOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> & {
+> = ActiveLinkOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> &
+  LinkPropsChildren
+
+export interface LinkPropsChildren {
   // If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
   children?:
     | React.ReactNode
@@ -766,7 +806,7 @@ export type LinkProps<
       }) => React.ReactNode)
 }
 
-type LinkComponentProps<TComp> = React.PropsWithoutRef<
+type LinkComponentReactProps<TComp> = React.PropsWithoutRef<
   TComp extends React.FC<infer TProps> | React.Component<infer TProps>
     ? TProps
     : TComp extends keyof JSX.IntrinsicElements
@@ -783,15 +823,24 @@ type LinkComponentProps<TComp> = React.PropsWithoutRef<
         : never
   >
 
-export type LinkComponent<TComp> = <
+export type LinkComponentProps<
+  TComp,
   TRouter extends AnyRouter = RegisteredRouter,
-  TFrom extends RoutePaths<TRouter['routeTree']> | string = string,
+  TFrom extends string = string,
   TTo extends string = '',
-  TMaskFrom extends RoutePaths<TRouter['routeTree']> | string = TFrom,
+  TMaskFrom extends string = TFrom,
+  TMaskTo extends string = '',
+> = LinkComponentReactProps<TComp> &
+  LinkProps<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>
+
+export type LinkComponent<TComp> = <
+  TRouter extends RegisteredRouter = RegisteredRouter,
+  TFrom extends string = string,
+  TTo extends string = '',
+  TMaskFrom extends string = TFrom,
   TMaskTo extends string = '',
 >(
-  props: LinkProps<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> &
-    LinkComponentProps<TComp>,
+  props: LinkComponentProps<TComp, TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
 ) => 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 322d1cb19..55e156ebc 100644
--- a/packages/react-router/src/route.ts
+++ b/packages/react-router/src/route.ts
@@ -57,16 +57,16 @@ export type RouteOptions<
   TParentRoute extends AnyRoute = AnyRoute,
   TCustomId extends string = string,
   TPath extends string = string,
-  TSearchSchemaInput extends Record<string, any> = {},
-  TSearchSchema extends Record<string, any> = {},
+  TSearchSchemaInput = {},
+  TSearchSchema = {},
   TSearchSchemaUsed = {},
   TFullSearchSchemaInput = TSearchSchemaUsed,
   TFullSearchSchema = TSearchSchema,
   TParams = AnyPathParams,
   TAllParams = TParams,
-  TRouteContextReturn extends RouteContext = RouteContext,
+  TRouteContextReturn = RouteContext,
   TRouteContext = RouteContext,
-  TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
+  TRouterContext = AnyContext,
   TAllContext = AnyContext,
   TLoaderDeps extends Record<string, any> = {},
   TLoaderDataReturn = unknown,
@@ -109,14 +109,14 @@ export type ParamsFallback<
 export type FileBaseRouteOptions<
   TParentRoute extends AnyRoute = AnyRoute,
   TPath extends string = string,
-  TSearchSchemaInput extends Record<string, any> = {},
-  TSearchSchema extends Record<string, any> = {},
+  TSearchSchemaInput = {},
+  TSearchSchema = {},
   TFullSearchSchema = TSearchSchema,
   TParams = {},
   TAllParams = ParamsFallback<TPath, TParams>,
-  TRouteContextReturn extends RouteContext = RouteContext,
+  TRouteContextReturn = RouteContext,
   TRouteContext = RouteContext,
-  TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
+  TRouterContext = AnyContext,
   TAllContext = AnyContext,
   TLoaderDeps extends Record<string, any> = {},
   TLoaderDataReturn = unknown,
@@ -173,16 +173,16 @@ export type BaseRouteOptions<
   TParentRoute extends AnyRoute = AnyRoute,
   TCustomId extends string = string,
   TPath extends string = string,
-  TSearchSchemaInput extends Record<string, any> = {},
-  TSearchSchema extends Record<string, any> = {},
+  TSearchSchemaInput = {},
+  TSearchSchema = {},
   TSearchSchemaUsed = {},
   TFullSearchSchemaInput = TSearchSchemaUsed,
   TFullSearchSchema = TSearchSchema,
   TParams = {},
   TAllParams = ParamsFallback<TPath, TParams>,
-  TRouteContextReturn extends RouteContext = RouteContext,
+  TRouteContextReturn = RouteContext,
   TRouteContext = RouteContext,
-  TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
+  TRouterContext = AnyContext,
   TAllContext = AnyContext,
   TLoaderDeps extends Record<string, any> = {},
   TLoaderDataReturn = unknown,
@@ -209,8 +209,8 @@ type BeforeLoadFn<
   in out TFullSearchSchema,
   in out TParentRoute extends AnyRoute,
   in out TAllParams,
-  TRouteContextReturn extends RouteContext,
-  in out TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
+  TRouteContextReturn,
+  in out TRouterContext = AnyContext,
   in out TContext = IsAny<TParentRoute['types']['allContext'], TRouterContext>,
 > = (opts: {
   search: TFullSearchSchema
@@ -393,25 +393,54 @@ export type InferFullSearchSchemaInput<TRoute> = TRoute extends {
   ? TFullSearchSchemaInput
   : {}
 
+export type ResolveSearchSchemaUsed<TSearchSchemaInput, TSearchSchema> =
+  TSearchSchemaInput extends SearchSchemaInput
+    ? Omit<TSearchSchemaInput, keyof SearchSchemaInput>
+    : TSearchSchema
+
 export type ResolveFullSearchSchema<
   TParentRoute extends AnyRoute,
   TSearchSchema,
-> = Assign<
-  TParentRoute['id'] extends RootRouteId
-    ? Omit<TParentRoute['types']['searchSchema'], keyof RootSearchSchema>
-    : TParentRoute['types']['fullSearchSchema'],
-  TSearchSchema
->
+> = unknown extends TParentRoute
+  ? Omit<TSearchSchema, keyof RootSearchSchema>
+  : Assign<
+      TParentRoute['id'] extends RootRouteId
+        ? Omit<TParentRoute['types']['searchSchema'], keyof RootSearchSchema>
+        : TParentRoute['types']['fullSearchSchema'],
+      TSearchSchema
+    >
 
 export type ResolveFullSearchSchemaInput<
   TParentRoute extends AnyRoute,
   TSearchSchemaUsed,
-> = Assign<
-  TParentRoute['id'] extends RootRouteId
-    ? Omit<TParentRoute['types']['searchSchemaInput'], keyof RootSearchSchema>
-    : TParentRoute['types']['fullSearchSchemaInput'],
-  TSearchSchemaUsed
->
+> = unknown extends TParentRoute
+  ? Omit<TSearchSchemaUsed, keyof RootSearchSchema>
+  : Assign<
+      TParentRoute['id'] extends RootRouteId
+        ? Omit<
+            TParentRoute['types']['searchSchemaInput'],
+            keyof RootSearchSchema
+          >
+        : TParentRoute['types']['fullSearchSchemaInput'],
+      TSearchSchemaUsed
+    >
+
+export type ResolveRouteContext<TRouteContextReturn> = [
+  TRouteContextReturn,
+] extends [never]
+  ? RouteContext
+  : TRouteContextReturn
+
+export type ResolveAllContext<
+  TParentRoute extends AnyRoute,
+  TRouteContext,
+> = Assign<IsAny<TParentRoute['types']['allContext'], {}>, TRouteContext>
+
+export type ResolveLoaderData<TLoaderDataReturn> = [TLoaderDataReturn] extends [
+  never,
+]
+  ? undefined
+  : TLoaderDataReturn
 
 export interface AnyRoute
   extends Route<
@@ -437,8 +466,12 @@ export interface AnyRoute
     any
   > {}
 
-// eslint-disable-next-line @typescript-eslint/naming-convention
-export type MergeFromFromParent<T, U> = IsAny<T, U, T & U>
+export type ResolveAllParamsFromParent<
+  TParentRoute extends AnyRoute,
+  TParams,
+> = unknown extends TParentRoute
+  ? TParams
+  : Assign<TParentRoute['types']['allParams'], TParams>
 
 export type ResolveAllParams<TParentRoute extends AnyRoute, TParams> =
   Record<never, string> extends TParentRoute['types']['allParams']
@@ -566,11 +599,12 @@ export class Route<
     TCustomId,
     TPath
   >,
-  in out TSearchSchemaInput extends RouteConstraints['TSearchSchema'] = {},
-  in out TSearchSchema extends RouteConstraints['TSearchSchema'] = {},
-  in out TSearchSchemaUsed = TSearchSchemaInput extends SearchSchemaInput
-    ? Omit<TSearchSchemaInput, keyof SearchSchemaInput>
-    : TSearchSchema,
+  in out TSearchSchemaInput = {},
+  in out TSearchSchema = {},
+  in out TSearchSchemaUsed = ResolveSearchSchemaUsed<
+    TSearchSchemaInput,
+    TSearchSchema
+  >,
   in out TFullSearchSchemaInput = ResolveFullSearchSchemaInput<
     TParentRoute,
     TSearchSchemaUsed
@@ -581,21 +615,17 @@ export class Route<
   >,
   in out TParams = Record<ParsePathParams<TPath>, string>,
   in out TAllParams = ResolveAllParams<TParentRoute, TParams>,
-  TRouteContextReturn extends RouteConstraints['TRouteContext'] = RouteContext,
-  in out TRouteContext = [TRouteContextReturn] extends [never]
-    ? RouteContext
-    : TRouteContextReturn,
+  TRouteContextReturn = RouteContext,
+  in out TRouteContext = ResolveRouteContext<TRouteContextReturn>,
   in out TAllContext = Assign<
     IsAny<TParentRoute['types']['allContext'], {}>,
     TRouteContext
   >,
-  in out TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
+  in out TRouterContext = AnyContext,
   in out TLoaderDeps extends Record<string, any> = {},
   TLoaderDataReturn = unknown,
-  in out TLoaderData = [TLoaderDataReturn] extends [never]
-    ? undefined
-    : TLoaderDataReturn,
-  in out TChildren extends RouteConstraints['TChildren'] = unknown,
+  in out TLoaderData = ResolveLoaderData<TLoaderDataReturn>,
+  in out TChildren = unknown,
 > {
   isRoot: TParentRoute extends Route<any> ? true : false
   options: RouteOptions<
@@ -764,7 +794,11 @@ export class Route<
     this.to = fullPath as TrimPathRight<TFullPath>
   }
 
-  addChildren = <const TNewChildren extends ReadonlyArray<AnyRoute>>(
+  addChildren = <
+    const TNewChildren extends
+      | Record<string, AnyRoute>
+      | ReadonlyArray<AnyRoute>,
+  >(
     children: TNewChildren,
   ): Route<
     TParentRoute,
@@ -788,7 +822,9 @@ export class Route<
     TLoaderData,
     TNewChildren
   > => {
-    this.children = children as any
+    this.children = (
+      Array.isArray(children) ? children : Object.values(children)
+    ) as any
     return this as any
   }
 
@@ -907,11 +943,12 @@ export function createRoute<
     TCustomId,
     TPath
   >,
-  TSearchSchemaInput extends RouteConstraints['TSearchSchema'] = {},
-  TSearchSchema extends RouteConstraints['TSearchSchema'] = {},
-  TSearchSchemaUsed = TSearchSchemaInput extends SearchSchemaInput
-    ? Omit<TSearchSchemaInput, keyof SearchSchemaInput>
-    : TSearchSchema,
+  TSearchSchemaInput = {},
+  TSearchSchema = {},
+  TSearchSchemaUsed = ResolveSearchSchemaUsed<
+    TSearchSchemaInput,
+    TSearchSchema
+  >,
   TFullSearchSchemaInput = ResolveFullSearchSchemaInput<
     TParentRoute,
     TSearchSchemaUsed
@@ -919,21 +956,17 @@ export function createRoute<
   TFullSearchSchema = ResolveFullSearchSchema<TParentRoute, TSearchSchema>,
   TParams = Record<ParsePathParams<TPath>, string>,
   TAllParams = ResolveAllParams<TParentRoute, TParams>,
-  TRouteContextReturn extends RouteConstraints['TRouteContext'] = RouteContext,
-  TRouteContext = [TRouteContextReturn] extends [never]
-    ? RouteContext
-    : TRouteContextReturn,
+  TRouteContextReturn = RouteContext,
+  TRouteContext = ResolveRouteContext<TRouteContextReturn>,
   TAllContext = Assign<
     IsAny<TParentRoute['types']['allContext'], {}>,
     TRouteContext
   >,
-  TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
+  TRouterContext = AnyContext,
   TLoaderDeps extends Record<string, any> = {},
   TLoaderDataReturn = unknown,
-  TLoaderData = [TLoaderDataReturn] extends [never]
-    ? undefined
-    : TLoaderDataReturn,
-  TChildren extends RouteConstraints['TChildren'] = unknown,
+  TLoaderData = ResolveLoaderData<TLoaderDataReturn>,
+  TChildren = unknown,
 >(
   options: RouteOptions<
     TParentRoute,
@@ -982,19 +1015,15 @@ export function createRoute<
 export type AnyRootRoute = RootRoute<any, any, any, any, any, any, any, any>
 
 export type RootRouteOptions<
-  TSearchSchemaInput extends Record<string, any> = RootSearchSchema,
-  TSearchSchema extends Record<string, any> = RootSearchSchema,
-  TSearchSchemaUsed extends Record<string, any> = RootSearchSchema,
-  TRouteContextReturn extends RouteContext = RouteContext,
-  TRouteContext extends RouteContext = [TRouteContextReturn] extends [never]
-    ? RouteContext
-    : TRouteContextReturn,
-  TRouterContext extends {} = {},
+  TSearchSchemaInput = RootSearchSchema,
+  TSearchSchema = RootSearchSchema,
+  TSearchSchemaUsed = RootSearchSchema,
+  TRouteContextReturn = RouteContext,
+  TRouteContext = ResolveRouteContext<TRouteContextReturn>,
+  TRouterContext = {},
   TLoaderDeps extends Record<string, any> = {},
   TLoaderDataReturn = unknown,
-  TLoaderData = [TLoaderDataReturn] extends [never]
-    ? undefined
-    : TLoaderDataReturn,
+  TLoaderData = ResolveLoaderData<TLoaderDataReturn>,
 > = Omit<
   RouteOptions<
     any, // TParentRoute
@@ -1073,21 +1102,15 @@ export type RootSearchSchema = {
 }
 
 export class RootRoute<
-  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,
-  in out TRouteContext extends RouteContext = [TRouteContextReturn] extends [
-    never,
-  ]
-    ? RouteContext
-    : TRouteContextReturn,
-  in out TRouterContext extends {} = {},
+  in out TSearchSchemaInput = RootSearchSchema,
+  in out TSearchSchema = RootSearchSchema,
+  in out TSearchSchemaUsed = RootSearchSchema,
+  TRouteContextReturn = RouteContext,
+  in out TRouteContext = ResolveRouteContext<TRouteContextReturn>,
+  in out TRouterContext = {},
   TLoaderDeps extends Record<string, any> = {},
   TLoaderDataReturn = unknown,
-  in out TLoaderData = [TLoaderDataReturn] extends [never]
-    ? undefined
-    : TLoaderDataReturn,
+  in out TLoaderData = ResolveLoaderData<TLoaderDataReturn>,
 > extends Route<
   any, // TParentRoute
   '/', // TPath
@@ -1131,19 +1154,15 @@ export class RootRoute<
 }
 
 export function createRootRoute<
-  TSearchSchemaInput extends Record<string, any> = RootSearchSchema,
-  TSearchSchema extends Record<string, any> = RootSearchSchema,
-  TSearchSchemaUsed extends Record<string, any> = RootSearchSchema,
-  TRouteContextReturn extends RouteContext = RouteContext,
-  TRouteContext extends RouteContext = [TRouteContextReturn] extends [never]
-    ? RouteContext
-    : TRouteContextReturn,
-  TRouterContext extends {} = {},
+  TSearchSchemaInput = RootSearchSchema,
+  TSearchSchema = RootSearchSchema,
+  TSearchSchemaUsed = RootSearchSchema,
+  TRouteContextReturn = RouteContext,
+  TRouteContext = ResolveRouteContext<TRouteContextReturn>,
+  TRouterContext = {},
   TLoaderDeps extends Record<string, any> = {},
   TLoaderDataReturn = unknown,
-  TLoaderData = [TLoaderDataReturn] extends [never]
-    ? undefined
-    : TLoaderDataReturn,
+  TLoaderData = ResolveLoaderData<TLoaderDataReturn>,
 >(
   options?: Omit<
     RouteOptions<
@@ -1276,8 +1295,7 @@ export type AsyncRouteComponent<TProps> = SyncRouteComponent<TProps> & {
   preload?: () => Promise<void>
 }
 
-export type RouteComponent<TProps = any> = SyncRouteComponent<TProps> &
-  AsyncRouteComponent<TProps>
+export type RouteComponent<TProps = any> = AsyncRouteComponent<TProps>
 
 export type ErrorRouteComponent = RouteComponent<ErrorComponentProps>
 
@@ -1286,27 +1304,24 @@ export type NotFoundRouteComponent = SyncRouteComponent<NotFoundRouteProps>
 export class NotFoundRoute<
   TParentRoute extends AnyRootRoute,
   TSearchSchemaInput extends Record<string, any> = {},
-  TSearchSchema extends RouteConstraints['TSearchSchema'] = {},
-  TSearchSchemaUsed extends RouteConstraints['TSearchSchema'] = {},
-  TFullSearchSchemaInput extends
-    RouteConstraints['TFullSearchSchema'] = ResolveFullSearchSchemaInput<
+  TSearchSchema = {},
+  TSearchSchemaUsed = {},
+  TFullSearchSchemaInput = ResolveFullSearchSchemaInput<
     TParentRoute,
     TSearchSchemaUsed
   >,
   TFullSearchSchema = ResolveFullSearchSchema<TParentRoute, TSearchSchema>,
-  TRouteContextReturn extends RouteConstraints['TRouteContext'] = AnyContext,
-  TRouteContext extends RouteConstraints['TRouteContext'] = RouteContext,
+  TRouteContextReturn = AnyContext,
+  TRouteContext = RouteContext,
   TAllContext = Assign<
     IsAny<TParentRoute['types']['allContext'], {}>,
     TRouteContext
   >,
-  TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
+  TRouterContext = AnyContext,
   TLoaderDeps extends Record<string, any> = {},
   TLoaderDataReturn = unknown,
-  TLoaderData = [TLoaderDataReturn] extends [never]
-    ? undefined
-    : TLoaderDataReturn,
-  TChildren extends RouteConstraints['TChildren'] = unknown,
+  TLoaderData = ResolveLoaderData<TLoaderDataReturn>,
+  TChildren = unknown,
 > extends Route<
   TParentRoute,
   '/404',
diff --git a/packages/react-router/src/routeInfo.ts b/packages/react-router/src/routeInfo.ts
index 91d8edf66..3de97f2e9 100644
--- a/packages/react-router/src/routeInfo.ts
+++ b/packages/react-router/src/routeInfo.ts
@@ -5,19 +5,28 @@ import type { UnionToIntersection, UnionToTuple } from './utils'
 export type ParseRoute<TRouteTree, TAcc = TRouteTree> = TRouteTree extends {
   types: { children: infer TChildren }
 }
-  ? TChildren extends ReadonlyArray<any>
-    ? ParseRoute<TChildren[number], TAcc | TChildren[number]>
-    : TAcc
+  ? unknown extends TChildren
+    ? TAcc
+    : TChildren extends ReadonlyArray<any>
+      ? ParseRoute<TChildren[number], TAcc | TChildren[number]>
+      : ParseRoute<
+          TChildren[keyof TChildren],
+          TAcc | TChildren[keyof TChildren]
+        >
   : TAcc
 
 export type ParseRouteWithoutBranches<TRouteTree> =
   ParseRoute<TRouteTree> extends infer TRoute extends AnyRoute
     ? TRoute extends any
-      ? TRoute['types']['children'] extends ReadonlyArray<any>
-        ? '/' extends TRoute['types']['children'][number]['path']
-          ? never
-          : TRoute
-        : TRoute
+      ? unknown extends TRoute['types']['children']
+        ? TRoute
+        : TRoute['types']['children'] extends ReadonlyArray<any>
+          ? '/' extends TRoute['types']['children'][number]['path']
+            ? never
+            : TRoute
+          : '/' extends TRoute['types']['children'][keyof TRoute['types']['children']]['path']
+            ? never
+            : TRoute
       : never
     : never
 
@@ -32,19 +41,14 @@ export type RouteById<TRouteTree extends AnyRoute, TId> = Extract<
 
 export type RouteIds<TRouteTree extends AnyRoute> = ParseRoute<TRouteTree>['id']
 
-export type CatchAllPaths<TRouteTree extends AnyRoute> = Record<
-  '.' | '..' | '',
-  ParseRoute<TRouteTree>
->
+export type CatchAllPaths = '.' | '..' | ''
 
 export type RoutesByPath<TRouteTree extends AnyRoute> = {
   [K in ParseRoute<TRouteTree> as K['fullPath']]: K
-} & CatchAllPaths<TRouteTree>
+}
 
 export type RouteByPath<TRouteTree extends AnyRoute, TPath> = Extract<
-  string extends TPath
-    ? ParseRoute<TRouteTree>
-    : RoutesByPath<TRouteTree>[TPath],
+  RoutesByPath<TRouteTree>[TPath],
   AnyRoute
 >
 
@@ -103,18 +107,16 @@ export type RoutesByToPath<TRouter extends AnyRouter> = {
     TRouter,
     TRoute
   >]: TRoute
-} & CatchAllPaths<TRouter['routeTree']>
+}
 
 export type RouteByToPath<TRouter extends AnyRouter, TTo> = Extract<
-  string extends TTo
-    ? ParseRouteWithoutBranches<TRouter['routeTree']>
-    : RoutesByToPath<TRouter>[TTo],
+  RoutesByToPath<TRouter>[TTo],
   AnyRoute
 >
 
-export type RoutePathsAutoComplete<TRouteTree extends AnyRoute, T> =
+export type RoutePathsAutoComplete<TRouter extends AnyRouter, T> =
   | (string extends T ? T & {} : T)
-  | RoutePaths<TRouteTree>
+  | RoutePaths<TRouter['routeTree']>
 
 // eslint-disable-next-line @typescript-eslint/naming-convention
 type UnionizeCollisions<T, U> = {
diff --git a/packages/react-router/src/useNavigate.tsx b/packages/react-router/src/useNavigate.tsx
index d60627ba0..2ae57c303 100644
--- a/packages/react-router/src/useNavigate.tsx
+++ b/packages/react-router/src/useNavigate.tsx
@@ -20,7 +20,7 @@ export type UseNavigateResult<TDefaultFrom extends string> = <
 export function useNavigate<
   TDefaultFrom extends string = string,
 >(_defaultOpts?: {
-  from?: RoutePathsAutoComplete<RegisteredRouter['routeTree'], TDefaultFrom>
+  from?: RoutePathsAutoComplete<RegisteredRouter, TDefaultFrom>
 }): UseNavigateResult<TDefaultFrom> {
   const router = useRouter()
 
@@ -32,7 +32,7 @@ export function useNavigate<
       })
     },
     [router],
-  )
+  ) as UseNavigateResult<TDefaultFrom>
 }
 
 // NOTE: I don't know of anyone using this. It's undocumented, so let's wait until someone needs it
diff --git a/packages/router-generator/src/generator.ts b/packages/router-generator/src/generator.ts
index 8da141522..b1e2955c3 100644
--- a/packages/router-generator/src/generator.ts
+++ b/packages/router-generator/src/generator.ts
@@ -417,7 +417,7 @@ export async function generator(config: Config) {
 
       if (node.children?.length) {
         const childConfigs = buildRouteConfig(node.children, depth + 1)
-        return `${route}.addChildren([${spaces(depth * 4)}${childConfigs}])`
+        return `${route}: ${route}.addChildren({${spaces(depth * 4)}${childConfigs}})`
       }
 
       return route
@@ -583,7 +583,16 @@ export async function generator(config: Config) {
   interface FileRoutesByPath {
     ${routeNodes
       .map((routeNode) => {
-        return `'${removeTrailingUnderscores(routeNode.routePath)}': {
+        const filePathId = removeTrailingUnderscores(routeNode.routePath)
+        const id = removeGroups(filePathId ?? '')
+        const fullPath = removeGroups(
+          removeUnderscores(removeLayoutSegments(routeNode.routePath)) ?? '',
+        )
+
+        return `'${filePathId}': {
+          id: '${id}'
+          path: '${routeNode.cleanedPath}'
+          fullPath: '${fullPath}'
           preLoaderRoute: typeof ${routeNode.variableName}Import
           parentRoute: typeof ${
             routeNode.isVirtualParentRequired
@@ -599,7 +608,7 @@ export async function generator(config: Config) {
 }`,
         ]),
     '// Create and export the route tree',
-    `export const routeTree = rootRoute.addChildren([${routeConfigChildrenText}])`,
+    `export const routeTree = rootRoute.addChildren({${routeConfigChildrenText}})`,
     ...config.routeTreeFileFooter,
   ]
     .filter(Boolean)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6f2c892be..4c43a263a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -700,6 +700,9 @@ importers:
 
   examples/react/large-file-based:
     dependencies:
+      '@tanstack/react-query':
+        specifier: ^5.17.8
+        version: 5.36.0(react@18.2.0)
       '@tanstack/react-router':
         specifier: ^1.32.0
         version: link:../../../packages/react-router
@@ -1507,7 +1510,7 @@ importers:
         version: 1.3.1
       vinxi:
         specifier: 0.2.1
-        version: 0.2.1(@opentelemetry/api@1.8.0)(@types/node@20.12.11)(preact@10.21.0)(rollup@4.17.2)
+        version: 0.2.1(@types/node@20.12.11)(preact@10.21.0)(rollup@4.17.2)
     devDependencies:
       '@testing-library/react':
         specifier: ^15.0.2
@@ -4561,19 +4564,6 @@ packages:
       terser: 5.27.0
     dev: false
 
-  /@rollup/plugin-wasm@6.2.2(rollup@4.17.2):
-    resolution: {integrity: sha512-gpC4R1G9Ni92ZIRTexqbhX7U+9estZrbhP+9SRb0DW9xpB9g7j34r+J2hqrcW/lRI7dJaU84MxZM0Rt82tqYPQ==}
-    engines: {node: '>=14.0.0'}
-    peerDependencies:
-      rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
-    peerDependenciesMeta:
-      rollup:
-        optional: true
-    dependencies:
-      '@rollup/pluginutils': 5.1.0(rollup@4.17.2)
-      rollup: 4.17.2
-    dev: false
-
   /@rollup/plugin-wasm@6.2.2(rollup@4.9.6):
     resolution: {integrity: sha512-gpC4R1G9Ni92ZIRTexqbhX7U+9estZrbhP+9SRb0DW9xpB9g7j34r+J2hqrcW/lRI7dJaU84MxZM0Rt82tqYPQ==}
     engines: {node: '>=14.0.0'}
@@ -5197,6 +5187,10 @@ packages:
     resolution: {integrity: sha512-V4hQv4jmRwbji9wo3F6/JQEjbWLUlv2sE2K5R49girEyok71ksDnVHbQiAvp4+FbovMY8A3IbP3cH3jqAUe/BQ==}
     dev: false
 
+  /@tanstack/query-core@5.36.0:
+    resolution: {integrity: sha512-B5BD3pg/mztDR36i77hGcyySKKeYrbM5mnogOROTBi1SUml5ByRK7PGUUl16vvubvQC+mSnqziFG/VIy/DE3FQ==}
+    dev: false
+
   /@tanstack/query-devtools@5.17.7:
     resolution: {integrity: sha512-TfgvOqza5K7Sk6slxqkRIvXlEJoUoPSsGGwpuYSrpqgSwLSSvPPpZhq7hv7hcY5IvRoTNGoq6+MT01C/jILqoQ==}
     dev: false
@@ -5254,6 +5248,15 @@ packages:
       react: 18.2.0
     dev: false
 
+  /@tanstack/react-query@5.36.0(react@18.2.0):
+    resolution: {integrity: sha512-BATvtM0rohwg7pRHUnxgeDiwLWRGZ8OM/4y8LImHVpecQWoH6Uhytu3Z8YV6V7hQ1sMQBFcUrGE1/e4MxR6YiA==}
+    peerDependencies:
+      react: ^18.0.0
+    dependencies:
+      '@tanstack/query-core': 5.36.0
+      react: 18.2.0
+    dev: false
+
   /@tanstack/react-store@0.2.1(react-dom@18.2.0)(react@18.2.0):
     resolution: {integrity: sha512-tEbMCQjbeVw9KOP/202LfqZMSNAVi6zYkkp1kBom8nFuMx/965Hzes3+6G6b/comCwVxoJU8Gg9IrcF8yRPthw==}
     peerDependencies:
@@ -10651,100 +10654,6 @@ packages:
       - uWebSockets.js
     dev: false
 
-  /nitropack@2.8.1(@opentelemetry/api@1.8.0):
-    resolution: {integrity: sha512-pODv2kEEzZSDQR+1UMXbGyNgMedUDq/qUomtiAnQKQvLy52VGlecXO1xDfH3i0kP1yKEcKTnWsx1TAF5gHM7xQ==}
-    engines: {node: ^16.11.0 || >=17.0.0}
-    hasBin: true
-    peerDependencies:
-      xml2js: ^0.6.2
-    peerDependenciesMeta:
-      xml2js:
-        optional: true
-    dependencies:
-      '@cloudflare/kv-asset-handler': 0.3.2
-      '@netlify/functions': 2.6.3(@opentelemetry/api@1.8.0)
-      '@rollup/plugin-alias': 5.1.0(rollup@4.17.2)
-      '@rollup/plugin-commonjs': 25.0.7(rollup@4.17.2)
-      '@rollup/plugin-inject': 5.0.5(rollup@4.17.2)
-      '@rollup/plugin-json': 6.1.0(rollup@4.17.2)
-      '@rollup/plugin-node-resolve': 15.2.3(rollup@4.17.2)
-      '@rollup/plugin-replace': 5.0.5(rollup@4.17.2)
-      '@rollup/plugin-terser': 0.4.4(rollup@4.17.2)
-      '@rollup/plugin-wasm': 6.2.2(rollup@4.17.2)
-      '@rollup/pluginutils': 5.1.0(rollup@4.17.2)
-      '@types/http-proxy': 1.17.14
-      '@vercel/nft': 0.24.4
-      archiver: 6.0.1
-      c12: 1.10.0
-      chalk: 5.3.0
-      chokidar: 3.6.0
-      citty: 0.1.6
-      consola: 3.2.3
-      cookie-es: 1.1.0
-      defu: 6.1.4
-      destr: 2.0.3
-      dot-prop: 8.0.2
-      esbuild: 0.19.12
-      escape-string-regexp: 5.0.0
-      estree-walker: 3.0.3
-      etag: 1.8.1
-      fs-extra: 11.2.0
-      globby: 14.0.1
-      gzip-size: 7.0.0
-      h3: 1.11.1
-      hookable: 5.5.3
-      httpxy: 0.1.5
-      is-primitive: 3.0.1
-      jiti: 1.21.0
-      klona: 2.0.6
-      knitwork: 1.1.0
-      listhen: 1.7.2
-      magic-string: 0.30.10
-      mime: 3.0.0
-      mlly: 1.7.0
-      mri: 1.2.0
-      node-fetch-native: 1.6.4
-      ofetch: 1.3.4
-      ohash: 1.1.3
-      openapi-typescript: 6.7.5
-      pathe: 1.1.2
-      perfect-debounce: 1.0.0
-      pkg-types: 1.1.1
-      pretty-bytes: 6.1.1
-      radix3: 1.1.2
-      rollup: 4.17.2
-      rollup-plugin-visualizer: 5.12.0(rollup@4.17.2)
-      scule: 1.3.0
-      semver: 7.6.2
-      serve-placeholder: 2.0.1
-      serve-static: 1.15.0
-      std-env: 3.7.0
-      ufo: 1.5.3
-      uncrypto: 0.1.3
-      unctx: 2.3.1
-      unenv: 1.9.0
-      unimport: 3.7.1(rollup@4.17.2)
-      unstorage: 1.10.2(ioredis@5.4.1)
-    transitivePeerDependencies:
-      - '@azure/app-configuration'
-      - '@azure/cosmos'
-      - '@azure/data-tables'
-      - '@azure/identity'
-      - '@azure/keyvault-secrets'
-      - '@azure/storage-blob'
-      - '@capacitor/preferences'
-      - '@netlify/blobs'
-      - '@opentelemetry/api'
-      - '@planetscale/database'
-      - '@upstash/redis'
-      - '@vercel/kv'
-      - encoding
-      - idb-keyval
-      - ioredis
-      - supports-color
-      - uWebSockets.js
-    dev: false
-
   /nitropack@2.9.6(@opentelemetry/api@1.8.0):
     resolution: {integrity: sha512-HP2PE0dREcDIBVkL8Zm6eVyrDd10/GI9hTL00PHvjUM8I9Y/2cv73wRDmxNyInfrx/CJKHATb2U/pQrqpzJyXA==}
     engines: {node: ^16.11.0 || >=17.0.0}
@@ -13657,7 +13566,7 @@ packages:
       - xml2js
     dev: false
 
-  /vinxi@0.2.1(@opentelemetry/api@1.8.0)(@types/node@20.12.11)(preact@10.21.0)(rollup@4.17.2):
+  /vinxi@0.2.1(@types/node@20.12.11)(preact@10.21.0)(rollup@4.17.2):
     resolution: {integrity: sha512-zrgFO2XuKpdoW5VwlbieeQ0YhzMuuYCJyFWFyj41h9BnymHZ5dnKElALvFQn5JVzHhyrsdmlm8GU7tIuH786hw==}
     hasBin: true
     dependencies:
@@ -13687,7 +13596,7 @@ packages:
       http-proxy: 1.18.1
       micromatch: 4.0.5
       mri: 1.2.0
-      nitropack: 2.8.1(@opentelemetry/api@1.8.0)
+      nitropack: 2.8.1
       node-fetch-native: 1.6.4
       path-to-regexp: 6.2.2
       pathe: 1.1.2
@@ -13716,7 +13625,6 @@ packages:
       - '@capacitor/preferences'
       - '@netlify/blobs'
       - '@nuxt/kit'
-      - '@opentelemetry/api'
       - '@planetscale/database'
       - '@testing-library/jest-dom'
       - '@types/node'


Test patch:
diff --git a/packages/react-router/tests/fileRoute.test-d.tsx b/packages/react-router/tests/fileRoute.test-d.tsx
index 7f4777469..f2f7fccc4 100644
--- a/packages/react-router/tests/fileRoute.test-d.tsx
+++ b/packages/react-router/tests/fileRoute.test-d.tsx
@@ -22,30 +22,51 @@ declare module '../src/fileRoute' {
     '/': {
       preLoaderRoute: typeof indexRoute
       parentRoute: typeof rootRoute
+      id: string
+      fullPath: string
+      path: string
     }
     '/(auth)/protected': {
       preLoaderRoute: typeof protectedRoute
       parentRoute: typeof rootRoute
+      id: '/protected'
+      fullPath: '/protected'
+      path: '(auth)/protected'
     }
     '/invoices': {
       preLoaderRoute: typeof invoicesRoute
       parentRoute: typeof indexRoute
+      id: '/invoices'
+      fullPath: '/invoices'
+      path: 'invoices'
     }
     '/invoices/$invoiceId': {
       preLoaderRoute: typeof invoiceRoute
       parentRoute: typeof invoicesRoute
+      id: '/invoices/$invoiceId'
+      fullPath: '/invoices/$invoiceId'
+      path: '/$invoiceId'
     }
     '/_postLayout': {
       preLoaderRoute: typeof postLayoutRoute
       parentRoute: typeof rootRoute
+      id: string
+      fullPath: string
+      path: string
     }
     '/_postLayout/posts': {
       preLoaderRoute: typeof postsRoute
       parentRoute: typeof postLayoutRoute
+      id: '/_postLayout/posts'
+      fullPath: '/posts'
+      path: '/posts'
     }
     '/_postLayout/posts/$postId_': {
       preLoaderRoute: typeof postRoute
       parentRoute: typeof postsRoute
+      id: '/_postLayout/posts/$postId_'
+      fullPath: '/posts/$postId'
+      path: '/$postId_'
     }
   }
 }
diff --git a/packages/react-router/tests/link.test-d.tsx b/packages/react-router/tests/link.test-d.tsx
index 3ea44acf3..d33d966ba 100644
--- a/packages/react-router/tests/link.test-d.tsx
+++ b/packages/react-router/tests/link.test-d.tsx
@@ -56,7 +56,7 @@ const detailRoute = createRoute({
   path: '$detailId',
 })
 
-const routeTree = rootRoute.addChildren([
+const routeTreeTuples = rootRoute.addChildren([
   postsRoute.addChildren([postRoute, postsIndexRoute]),
   invoicesRoute.addChildren([
     invoicesIndexRoute,
@@ -68,27 +68,45 @@ const routeTree = rootRoute.addChildren([
   indexRoute,
 ])
 
+const routeTreeObjects = rootRoute.addChildren({
+  postsRoute: postsRoute.addChildren({ postRoute, postsIndexRoute }),
+  invoicesRoute: invoicesRoute.addChildren({
+    invoicesIndexRoute,
+    invoiceRoute: invoiceRoute.addChildren({
+      invoiceEditRoute,
+      invoiceDetailsRoute: invoiceDetailsRoute.addChildren({ detailRoute }),
+    }),
+  }),
+  indexRoute,
+})
+
 const defaultRouter = createRouter({
-  routeTree,
+  routeTree: routeTreeTuples,
+})
+
+const defaultRouterObjects = createRouter({
+  routeTree: routeTreeObjects,
 })
 
 const routerAlwaysTrailingSlashes = createRouter({
-  routeTree,
+  routeTree: routeTreeTuples,
   trailingSlash: 'always',
 })
 
 const routerNeverTrailingSlashes = createRouter({
-  routeTree,
+  routeTree: routeTreeTuples,
   trailingSlash: 'never',
 })
 
 const routerPreserveTrailingSlashes = createRouter({
-  routeTree,
+  routeTree: routeTreeTuples,
   trailingSlash: 'preserve',
 })
 
 type DefaultRouter = typeof defaultRouter
 
+type DefaultRouterObjects = typeof defaultRouterObjects
+
 type RouterAlwaysTrailingSlashes = typeof routerAlwaysTrailingSlashes
 
 type RouterNeverTrailingSlashes = typeof routerNeverTrailingSlashes
@@ -121,6 +139,31 @@ test('when navigating to the root', () => {
       | undefined
     >()
 
+  expectTypeOf(Link<DefaultRouterObjects, string, '/'>)
+    .parameter(0)
+    .toHaveProperty('to')
+    .toEqualTypeOf<
+      | '../'
+      | './'
+      | ''
+      | '/'
+      | '/invoices'
+      | '/invoices/$invoiceId'
+      | '/invoices/$invoiceId/details/$detailId'
+      | '/invoices/$invoiceId/details'
+      | '/invoices/$invoiceId/edit'
+      | '/posts'
+      | '/posts/$postId'
+      | 'invoices'
+      | 'invoices/$invoiceId'
+      | 'invoices/$invoiceId/details/$detailId'
+      | 'invoices/$invoiceId/details'
+      | 'invoices/$invoiceId/edit'
+      | 'posts'
+      | 'posts/$postId'
+      | undefined
+    >()
+
   expectTypeOf(Link<RouterAlwaysTrailingSlashes, string, '/'>)
     .parameter(0)
     .toHaveProperty('to')
@@ -231,6 +274,25 @@ test('when navigating from a route with no params and no search to the root', ()
       | undefined
     >()
 
+  expectTypeOf(Link<DefaultRouterObjects, '/posts', '/'>)
+    .parameter(0)
+    .toHaveProperty('to')
+    .toEqualTypeOf<
+      | '../'
+      | './'
+      | '/'
+      | ''
+      | '/invoices'
+      | '/invoices/$invoiceId'
+      | '/invoices/$invoiceId/edit'
+      | '/invoices/$invoiceId/details'
+      | '/invoices/$invoiceId/details/$detailId'
+      | '/posts'
+      | '/posts/$postId'
+      | '$postId'
+      | undefined
+    >()
+
   expectTypeOf(Link<RouterAlwaysTrailingSlashes, '/posts/', '/'>)
     .parameter(0)
     .toHaveProperty('to')
@@ -304,6 +366,11 @@ test('when navigating from a route with no params and no search to the current r
     .toHaveProperty('to')
     .toEqualTypeOf<'./$postId' | undefined | './'>()
 
+  expectTypeOf(Link<DefaultRouterObjects, '/posts/', './'>)
+    .parameter(0)
+    .toHaveProperty('to')
+    .toEqualTypeOf<'./$postId' | undefined | './'>()
+
   expectTypeOf(Link<RouterAlwaysTrailingSlashes, '/posts/', './'>)
     .parameter(0)
     .toHaveProperty('to')
@@ -336,6 +403,21 @@ test('when navigating from a route with no params and no search to the parent ro
       | undefined
     >()
 
+  expectTypeOf(Link<DefaultRouterObjects, '/posts', '../'>)
+    .parameter(0)
+    .toHaveProperty('to')
+    .toEqualTypeOf<
+      | '../posts'
+      | '../posts/$postId'
+      | '../invoices/$invoiceId'
+      | '../invoices/$invoiceId/edit'
+      | '../invoices/$invoiceId/details'
+      | '../invoices/$invoiceId/details/$detailId'
+      | '../invoices'
+      | '../'
+      | undefined
+    >()
+
   expectTypeOf(Link<RouterAlwaysTrailingSlashes, '/posts', '../'>)
     .parameter(0)
     .toHaveProperty('to')
@@ -389,7 +471,7 @@ test('when navigating from a route with no params and no search to the parent ro
     >()
 })
 
-test('cannot navigate to a branch', () => {
+test('cannot navigate to a branch with an index', () => {
   expectTypeOf(Link<DefaultRouter, string, '/invoices/$invoiceId'>)
     .parameter(0)
     .toHaveProperty('to')
@@ -415,6 +497,31 @@ test('cannot navigate to a branch', () => {
       | undefined
     >()
 
+  expectTypeOf(Link<DefaultRouterObjects, string, '/invoices/$invoiceId'>)
+    .parameter(0)
+    .toHaveProperty('to')
+    .toEqualTypeOf<
+      | ''
+      | '/'
+      | 'posts'
+      | '/posts'
+      | '/posts/$postId'
+      | 'posts/$postId'
+      | 'invoices'
+      | '/invoices'
+      | '/invoices/$invoiceId'
+      | '/invoices/$invoiceId/edit'
+      | 'invoices/$invoiceId'
+      | 'invoices/$invoiceId/edit'
+      | '/invoices/$invoiceId/details'
+      | '/invoices/$invoiceId/details/$detailId'
+      | 'invoices/$invoiceId/details'
+      | 'invoices/$invoiceId/details/$detailId'
+      | './'
+      | '../'
+      | undefined
+    >()
+
   expectTypeOf(
     Link<RouterAlwaysTrailingSlashes, string, '/invoices/$invoiceId'>,
   )
@@ -510,8 +617,28 @@ test('cannot navigate to a branch', () => {
 })
 
 test('from autocompletes to all absolute routes', () => {
-  const TestLink = Link<DefaultRouter, '/', '/'>
-  expectTypeOf(TestLink)
+  const DefaultRouterLink = Link<DefaultRouter, '/', '/'>
+  const DefaultRouterObjectsLink = Link<DefaultRouterObjects, '/', '/'>
+
+  expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('from')
+    .toEqualTypeOf<
+      | '/'
+      | '/posts/$postId'
+      | '/posts/'
+      | '/'
+      | '/posts'
+      | '/invoices'
+      | '/invoices/'
+      | '/invoices/$invoiceId'
+      | '/invoices/$invoiceId/edit'
+      | '/invoices/$invoiceId/details'
+      | '/invoices/$invoiceId/details/$detailId'
+      | undefined
+    >()
+
+  expectTypeOf(DefaultRouterObjectsLink)
     .parameter(0)
     .toHaveProperty('from')
     .toEqualTypeOf<
@@ -532,6 +659,7 @@ test('from autocompletes to all absolute routes', () => {
 
 test('when navigating to the same route', () => {
   const DefaultRouterLink = Link<DefaultRouter, string, string>
+  const DefaultRouterObjectsLink = Link<DefaultRouterObjects, string, string>
   const RouterAlwaysTrailingSlashesLink = Link<
     RouterAlwaysTrailingSlashes,
     string,
@@ -552,6 +680,10 @@ test('when navigating to the same route', () => {
     .parameter(0)
     .not.toMatchTypeOf<{ params: unknown }>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .not.toMatchTypeOf<{ params: unknown }>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .not.toMatchTypeOf<{ params: unknown }>()
@@ -570,6 +702,12 @@ test('when navigating to the same route', () => {
     .extract<boolean>()
     .toEqualTypeOf<true>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toHaveProperty('params')
+    .extract<boolean>()
+    .toEqualTypeOf<true>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .toHaveProperty('params')
@@ -592,6 +730,10 @@ test('when navigating to the same route', () => {
     .parameter(0)
     .not.toMatchTypeOf<{ search: unknown }>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .not.toMatchTypeOf<{ search: unknown }>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .not.toMatchTypeOf<{ search: unknown }>()
@@ -610,6 +752,12 @@ test('when navigating to the same route', () => {
     .extract<boolean>()
     .toEqualTypeOf<true>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .extract<boolean>()
+    .toEqualTypeOf<true>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .toHaveProperty('search')
@@ -631,6 +779,8 @@ test('when navigating to the same route', () => {
 
 test('when navigating to the parent route', () => {
   const DefaultRouterLink = Link<DefaultRouter, string, '..'>
+  const DefaultRouterObjectsLink = Link<DefaultRouterObjects, string, '..'>
+
   const RouterAlwaysTrailingSlashesLink = Link<
     RouterAlwaysTrailingSlashes,
     string,
@@ -651,6 +801,10 @@ test('when navigating to the parent route', () => {
     .parameter(0)
     .not.toMatchTypeOf<{ params: unknown }>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .not.toMatchTypeOf<{ params: unknown }>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .not.toMatchTypeOf<{ params: unknown }>()
@@ -669,6 +823,12 @@ test('when navigating to the parent route', () => {
     .extract<boolean>()
     .toEqualTypeOf<true>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toHaveProperty('params')
+    .extract<boolean>()
+    .toEqualTypeOf<true>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .toHaveProperty('params')
@@ -691,6 +851,10 @@ test('when navigating to the parent route', () => {
     .parameter(0)
     .not.toMatchTypeOf<{ search: unknown }>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .not.toMatchTypeOf<{ search: unknown }>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .not.toMatchTypeOf<{ search: unknown }>()
@@ -709,6 +873,12 @@ test('when navigating to the parent route', () => {
     .extract<boolean>()
     .toEqualTypeOf<true>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toHaveProperty('search')
+    .extract<boolean>()
+    .toEqualTypeOf<true>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .toHaveProperty('search')
@@ -730,6 +900,11 @@ test('when navigating to the parent route', () => {
 
 test('when navigating from a route with params to the same route', () => {
   const DefaultRouterLink = Link<DefaultRouter, '/posts/$postId', string>
+  const DefaultRouterObjectsLink = Link<
+    DefaultRouterObjects,
+    '/posts/$postId',
+    string
+  >
 
   const RouterAlwaysTrailingSlashesLink = Link<
     RouterAlwaysTrailingSlashes,
@@ -753,6 +928,10 @@ test('when navigating from a route with params to the same route', () => {
     .parameter(0)
     .not.toMatchTypeOf<{ params: unknown }>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .not.toMatchTypeOf<{ params: unknown }>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .not.toMatchTypeOf<{ params: unknown }>()
@@ -771,6 +950,12 @@ test('when navigating from a route with params to the same route', () => {
     .extract<boolean>()
     .toEqualTypeOf<true>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toHaveProperty('params')
+    .extract<boolean>()
+    .toEqualTypeOf<true>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .toHaveProperty('params')
@@ -793,6 +978,12 @@ test('when navigating from a route with params to the same route', () => {
 test('when navigating to a route with params', () => {
   const DefaultRouterLink = Link<DefaultRouter, string, '/posts/$postId'>
 
+  const DefaultRouterObjectsLink = Link<
+    DefaultRouterObjects,
+    string,
+    '/posts/$postId'
+  >
+
   const RouterAlwaysTrailingSlashesLink = Link<
     RouterAlwaysTrailingSlashes,
     string,
@@ -815,6 +1006,10 @@ test('when navigating to a route with params', () => {
     .parameter(0)
     .toMatchTypeOf<{ params: unknown }>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toMatchTypeOf<{ params: unknown }>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .toMatchTypeOf<{ params: unknown }>()
@@ -831,6 +1026,10 @@ test('when navigating to a route with params', () => {
     .parameter(0)
     .toHaveProperty('params')
 
+  const defaultRouterObjectsLinkParams = expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toHaveProperty('params')
+
   const routerAlwaysTrailingSlashesLinkParams = expectTypeOf(
     RouterAlwaysTrailingSlashesLink,
   )
@@ -853,6 +1052,10 @@ test('when navigating to a route with params', () => {
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ postId: string }>()
 
+  defaultRouterObjectsLinkParams
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ postId: string }>()
+
   routerAlwaysTrailingSlashesLinkParams
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ postId: string }>()
@@ -867,6 +1070,8 @@ test('when navigating to a route with params', () => {
 
   defaultRouterLinkParams.returns.toEqualTypeOf<{ postId: string }>()
 
+  defaultRouterObjectsLinkParams.returns.toEqualTypeOf<{ postId: string }>()
+
   routerAlwaysTrailingSlashesLinkParams.returns.toEqualTypeOf<{
     postId: string
   }>()
@@ -888,6 +1093,15 @@ test('when navigating to a route with params', () => {
       | { invoiceId: string; detailId: string }
     >()
 
+  defaultRouterObjectsLinkParams
+    .parameter(0)
+    .toEqualTypeOf<
+      | {}
+      | { invoiceId: string }
+      | { postId: string }
+      | { invoiceId: string; detailId: string }
+    >()
+
   routerAlwaysTrailingSlashesLinkParams
     .parameter(0)
     .toEqualTypeOf<
@@ -923,6 +1137,12 @@ test('when navigating from a route with no params to a route with params', () =>
     './$invoiceId/edit'
   >
 
+  const DefaultRouterObjectsLink = Link<
+    DefaultRouterObjects,
+    '/invoices',
+    './$invoiceId/edit'
+  >
+
   const RouterAlwaysTrailingSlashesLink = Link<
     RouterAlwaysTrailingSlashes,
     '/invoices',
@@ -945,6 +1165,10 @@ test('when navigating from a route with no params to a route with params', () =>
     .parameter(0)
     .toMatchTypeOf<{ params: unknown }>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toMatchTypeOf<{ params: unknown }>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .toMatchTypeOf<{ params: unknown }>()
@@ -961,6 +1185,10 @@ test('when navigating from a route with no params to a route with params', () =>
     .parameter(0)
     .toHaveProperty('params')
 
+  const defaultRouterObjectsLinkParams = expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('params')
+
   const routerAlwaysTrailingSlashesLinkParams = expectTypeOf(
     RouterAlwaysTrailingSlashesLink,
   )
@@ -983,6 +1211,10 @@ test('when navigating from a route with no params to a route with params', () =>
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ invoiceId: string }>()
 
+  defaultRouterObjectsLinkParams
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ invoiceId: string }>()
+
   routerAlwaysTrailingSlashesLinkParams
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ invoiceId: string }>()
@@ -997,6 +1229,8 @@ test('when navigating from a route with no params to a route with params', () =>
 
   defaultRouterLinkParams.returns.toEqualTypeOf<{ invoiceId: string }>()
 
+  defaultRouterObjectsLinkParams.returns.toEqualTypeOf<{ invoiceId: string }>()
+
   routerAlwaysTrailingSlashesLinkParams.returns.toEqualTypeOf<{
     invoiceId: string
   }>()
@@ -1011,6 +1245,8 @@ test('when navigating from a route with no params to a route with params', () =>
 
   defaultRouterLinkParams.parameter(0).toEqualTypeOf<{}>()
 
+  defaultRouterObjectsLinkParams.parameter(0).toEqualTypeOf<{}>()
+
   routerAlwaysTrailingSlashesLinkParams.parameter(0).toEqualTypeOf<{}>()
 
   routerNeverTrailingSlashesLinkParams.parameter(0).toEqualTypeOf<{}>()
@@ -1025,6 +1261,12 @@ test('when navigating from a route to a route with the same params', () => {
     './edit'
   >
 
+  const DefaultRouterObjectsLink = Link<
+    DefaultRouterObjects,
+    '/invoices/$invoiceId',
+    './edit'
+  >
+
   const RouterAlwaysTrailingSlashesLink = Link<
     RouterAlwaysTrailingSlashes,
     '/invoices/$invoiceId',
@@ -1047,6 +1289,10 @@ test('when navigating from a route to a route with the same params', () => {
     .parameter(0)
     .toHaveProperty('params')
 
+  const defaultRouterObjectsLinkParams = expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('params')
+
   const routerAlwaysTrailingSlashesLinkParams = expectTypeOf(
     RouterAlwaysTrailingSlashesLink,
   )
@@ -1069,6 +1315,10 @@ test('when navigating from a route to a route with the same params', () => {
     .parameter(0)
     .not.toMatchTypeOf<{ params: unknown }>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .not.toMatchTypeOf<{ params: unknown }>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .not.toMatchTypeOf<{ params: unknown }>()
@@ -1085,6 +1335,10 @@ test('when navigating from a route to a route with the same params', () => {
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ invoiceId?: string | undefined } | undefined>()
 
+  defaultRouterObjectsLinkParams
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ invoiceId?: string | undefined } | undefined>()
+
   routerAlwaysTrailingSlashesLinkParams
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ invoiceId?: string | undefined } | undefined>()
@@ -1101,6 +1355,10 @@ test('when navigating from a route to a route with the same params', () => {
     invoiceId?: string | undefined
   }>()
 
+  defaultRouterObjectsLinkParams.returns.toEqualTypeOf<{
+    invoiceId?: string | undefined
+  }>()
+
   routerAlwaysTrailingSlashesLinkParams.returns.toEqualTypeOf<{
     invoiceId?: string | undefined
   }>()
@@ -1115,6 +1373,10 @@ test('when navigating from a route to a route with the same params', () => {
 
   defaultRouterLinkParams.parameter(0).toEqualTypeOf<{ invoiceId: string }>()
 
+  defaultRouterObjectsLinkParams
+    .parameter(0)
+    .toEqualTypeOf<{ invoiceId: string }>()
+
   routerAlwaysTrailingSlashesLinkParams
     .parameter(0)
     .toEqualTypeOf<{ invoiceId: string }>()
@@ -1135,6 +1397,12 @@ test('when navigating from a route with params to a route with different params'
     '../../posts/$postId'
   >
 
+  const DefaultRouterObjectsLink = Link<
+    DefaultRouterObjects,
+    '/invoices/$invoiceId',
+    '../../posts/$postId'
+  >
+
   const RouterAlwaysTrailingSlashesLink = Link<
     RouterAlwaysTrailingSlashes,
     '/invoices/$invoiceId',
@@ -1157,6 +1425,10 @@ test('when navigating from a route with params to a route with different params'
     .parameter(0)
     .toHaveProperty('params')
 
+  const defaultRouterObjectsLinkParams = expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('params')
+
   const routerAlwaysTrailingSlashesLinkParams = expectTypeOf(
     RouterAlwaysTrailingSlashesLink,
   )
@@ -1179,6 +1451,10 @@ test('when navigating from a route with params to a route with different params'
     .parameter(0)
     .toMatchTypeOf<{ params: unknown }>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toMatchTypeOf<{ params: unknown }>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .toMatchTypeOf<{ params: unknown }>()
@@ -1195,6 +1471,10 @@ test('when navigating from a route with params to a route with different params'
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ postId: string }>()
 
+  defaultRouterObjectsLinkParams
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ postId: string }>()
+
   routerAlwaysTrailingSlashesLinkParams
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ postId: string }>()
@@ -1209,6 +1489,8 @@ test('when navigating from a route with params to a route with different params'
 
   defaultRouterLinkParams.returns.toEqualTypeOf<{ postId: string }>()
 
+  defaultRouterObjectsLinkParams.returns.toEqualTypeOf<{ postId: string }>()
+
   routerAlwaysTrailingSlashesLinkParams.returns.toEqualTypeOf<{
     postId: string
   }>()
@@ -1223,6 +1505,10 @@ test('when navigating from a route with params to a route with different params'
 
   defaultRouterLinkParams.parameter(0).toEqualTypeOf<{ invoiceId: string }>()
 
+  defaultRouterObjectsLinkParams
+    .parameter(0)
+    .toEqualTypeOf<{ invoiceId: string }>()
+
   routerAlwaysTrailingSlashesLinkParams.parameter(0).toEqualTypeOf<{
     invoiceId: string
   }>()
@@ -1243,6 +1529,12 @@ test('when navigating from a route with params to a route with an additional par
     './details/$detailId'
   >
 
+  const DefaultRouterObjectsLink = Link<
+    DefaultRouterObjects,
+    '/invoices/$invoiceId',
+    './details/$detailId'
+  >
+
   const RouterAlwaysTrailingSlashesLink = Link<
     RouterAlwaysTrailingSlashes,
     '/invoices/$invoiceId',
@@ -1265,6 +1557,10 @@ test('when navigating from a route with params to a route with an additional par
     .parameter(0)
     .toHaveProperty('params')
 
+  const defaultRouterObjectsLinkParams = expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toHaveProperty('params')
+
   const routerAlwaysTrailingSlashesLinkParams = expectTypeOf(
     RouterAlwaysTrailingSlashesLink,
   )
@@ -1287,6 +1583,10 @@ test('when navigating from a route with params to a route with an additional par
     .parameter(0)
     .toMatchTypeOf<{ params: unknown }>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toMatchTypeOf<{ params: unknown }>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .toMatchTypeOf<{ params: unknown }>()
@@ -1303,6 +1603,10 @@ test('when navigating from a route with params to a route with an additional par
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ invoiceId?: string | undefined; detailId: string }>()
 
+  defaultRouterObjectsLinkParams
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ invoiceId?: string | undefined; detailId: string }>()
+
   routerAlwaysTrailingSlashesLinkParams
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ invoiceId?: string | undefined; detailId: string }>()
@@ -1320,6 +1624,11 @@ test('when navigating from a route with params to a route with an additional par
     detailId: string
   }>()
 
+  defaultRouterObjectsLinkParams.returns.toEqualTypeOf<{
+    invoiceId?: string
+    detailId: string
+  }>()
+
   routerAlwaysTrailingSlashesLinkParams.returns.toEqualTypeOf<{
     invoiceId?: string
     detailId: string
@@ -1343,6 +1652,12 @@ test('when navigating to a union of routes with params', () => {
     '/invoices/$invoiceId/edit' | '/posts/$postId'
   >
 
+  const DefaultRouterObjectsLink = Link<
+    DefaultRouterObjects,
+    string,
+    '/invoices/$invoiceId/edit' | '/posts/$postId'
+  >
+
   const RouterAlwaysTrailingSlashesLink = Link<
     RouterAlwaysTrailingSlashes,
     string,
@@ -1368,6 +1683,10 @@ test('when navigating to a union of routes with params', () => {
     .parameter(0)
     .toHaveProperty('params')
 
+  const defaultRouterObjectsLinkParams = expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('params')
+
   const routerAlwaysTrailingSlashesLinkParams = expectTypeOf(
     RouterAlwaysTrailingSlashesLink,
   )
@@ -1390,6 +1709,10 @@ test('when navigating to a union of routes with params', () => {
     .parameter(0)
     .not.toMatchTypeOf<{ params: unknown }>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .not.toMatchTypeOf<{ params: unknown }>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .not.toMatchTypeOf<{ params: unknown }>()
@@ -1406,6 +1729,10 @@ test('when navigating to a union of routes with params', () => {
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ invoiceId: string } | { postId: string } | undefined>()
 
+  defaultRouterObjectsLinkParams
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ invoiceId: string } | { postId: string } | undefined>()
+
   routerAlwaysTrailingSlashesLinkParams
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ invoiceId: string } | { postId: string } | undefined>()
@@ -1422,6 +1749,10 @@ test('when navigating to a union of routes with params', () => {
     { invoiceId: string } | { postId: string }
   >()
 
+  defaultRouterObjectsLinkParams.returns.toEqualTypeOf<
+    { invoiceId: string } | { postId: string }
+  >()
+
   routerAlwaysTrailingSlashesLinkParams.returns.toEqualTypeOf<
     { invoiceId: string } | { postId: string }
   >()
@@ -1443,6 +1774,15 @@ test('when navigating to a union of routes with params', () => {
       | { invoiceId: string; detailId: string }
     >()
 
+  defaultRouterObjectsLinkParams
+    .parameter(0)
+    .toEqualTypeOf<
+      | {}
+      | { invoiceId: string }
+      | { postId: string }
+      | { invoiceId: string; detailId: string }
+    >()
+
   routerAlwaysTrailingSlashesLinkParams
     .parameter(0)
     .toEqualTypeOf<
@@ -1478,6 +1818,12 @@ test('when navigating to a union of routes including the root', () => {
     '/' | '/invoices/$invoiceId/edit' | '/posts/$postId'
   >
 
+  const DefaultRouterObjectsLink = Link<
+    DefaultRouterObjects,
+    string,
+    '/' | '/invoices/$invoiceId/edit' | '/posts/$postId'
+  >
+
   const RouterAlwaysTrailingSlashesLink = Link<
     RouterAlwaysTrailingSlashes,
     string,
@@ -1504,6 +1850,10 @@ test('when navigating to a union of routes including the root', () => {
     .parameter(0)
     .toHaveProperty('params')
 
+  const defaultRouterObjectsLinkParams = expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('params')
+
   const routerAlwaysTrailingSlashesLinkParams = expectTypeOf(
     RouterAlwaysTrailingSlashesLink,
   )
@@ -1526,6 +1876,10 @@ test('when navigating to a union of routes including the root', () => {
     .parameter(0)
     .not.toMatchTypeOf<{ params: unknown }>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .not.toMatchTypeOf<{ params: unknown }>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .not.toMatchTypeOf<{ params: unknown }>()
@@ -1544,6 +1898,12 @@ test('when navigating to a union of routes including the root', () => {
       { invoiceId: string } | { postId: string } | {} | undefined
     >()
 
+  defaultRouterObjectsLinkParams
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<
+      { invoiceId: string } | { postId: string } | {} | undefined
+    >()
+
   routerAlwaysTrailingSlashesLinkParams
     .exclude<Function | boolean>()
     .toEqualTypeOf<
@@ -1566,6 +1926,10 @@ test('when navigating to a union of routes including the root', () => {
     { invoiceId: string } | { postId: string } | {}
   >()
 
+  defaultRouterObjectsLinkParams.returns.toEqualTypeOf<
+    { invoiceId: string } | { postId: string } | {}
+  >()
+
   routerAlwaysTrailingSlashesLinkParams.returns.toEqualTypeOf<
     { invoiceId: string } | { postId: string } | {}
   >()
@@ -1586,11 +1950,53 @@ test('when navigating to a union of routes including the root', () => {
       | { postId: string }
       | { invoiceId: string; detailId: string }
     >()
+
+  defaultRouterObjectsLinkParams
+    .parameter(0)
+    .toEqualTypeOf<
+      | {}
+      | { invoiceId: string }
+      | { postId: string }
+      | { invoiceId: string; detailId: string }
+    >()
+
+  routerAlwaysTrailingSlashesLinkParams
+    .parameter(0)
+    .toEqualTypeOf<
+      | {}
+      | { invoiceId: string }
+      | { postId: string }
+      | { invoiceId: string; detailId: string }
+    >()
+
+  routerNeverTrailingSlashesLinkParams
+    .parameter(0)
+    .toEqualTypeOf<
+      | {}
+      | { invoiceId: string }
+      | { postId: string }
+      | { invoiceId: string; detailId: string }
+    >()
+
+  routerPreserveTrailingSlashesLinkParams
+    .parameter(0)
+    .toEqualTypeOf<
+      | {}
+      | { invoiceId: string }
+      | { postId: string }
+      | { invoiceId: string; detailId: string }
+    >()
 })
 
 test('when navigating from a route with search params to the same route', () => {
   const DefaultRouterLink = Link<DefaultRouter, '/invoices/$invoiceId', string>
 
+  const DefaultRouterObjectsLink = Link<
+    DefaultRouterObjects,
+    '/invoices/$invoiceId',
+    string
+  >
+
   const RouterAlwaysTrailingSlashesLink = Link<
     RouterAlwaysTrailingSlashes,
     '/invoices/$invoiceId',
@@ -1613,6 +2019,10 @@ test('when navigating from a route with search params to the same route', () =>
     .parameter(0)
     .toMatchTypeOf<{ search: unknown }>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toMatchTypeOf<{ search: unknown }>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .toMatchTypeOf<{ search: unknown }>()
@@ -1631,6 +2041,10 @@ test('when navigating from a route with search params to the same route', () =>
     .extract<boolean>()
     .toEqualTypeOf<true>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toMatchTypeOf<{ search: unknown }>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .toHaveProperty('search')
@@ -1657,6 +2071,12 @@ test('when navigating to a route with search params', () => {
     '/invoices/$invoiceId/edit'
   >
 
+  const DefaultRouterObjectsLink = Link<
+    DefaultRouter,
+    string,
+    '/invoices/$invoiceId/edit'
+  >
+
   const RouterAlwaysTrailingSlashesLink = Link<
     RouterAlwaysTrailingSlashes,
     string,
@@ -1679,6 +2099,10 @@ test('when navigating to a route with search params', () => {
     .parameter(0)
     .toHaveProperty('search')
 
+  const defaultRouterObjectsLinkSearch = expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('search')
+
   const routerAlwaysTrailingSlashesLinkSearch = expectTypeOf(
     RouterAlwaysTrailingSlashesLink,
   )
@@ -1701,6 +2125,10 @@ test('when navigating to a route with search params', () => {
     .parameter(0)
     .toMatchTypeOf<{ search: unknown }>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toMatchTypeOf<{ search: unknown }>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .toMatchTypeOf<{ search: unknown }>()
@@ -1717,6 +2145,10 @@ test('when navigating to a route with search params', () => {
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ page: number }>()
 
+  defaultRouterObjectsLinkSearch
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ page: number }>()
+
   routerAlwaysTrailingSlashesLinkSearch
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ page: number }>()
@@ -1731,6 +2163,8 @@ test('when navigating to a route with search params', () => {
 
   defaultRouterLinkSearch.returns.toEqualTypeOf<{ page: number }>()
 
+  defaultRouterObjectsLinkSearch.returns.toEqualTypeOf<{ page: number }>()
+
   routerAlwaysTrailingSlashesLinkSearch.returns.toEqualTypeOf<{
     page: number
   }>()
@@ -1745,6 +2179,10 @@ test('when navigating to a route with search params', () => {
     .parameter(0)
     .toEqualTypeOf<{} | { page: number } | { page?: number }>()
 
+  defaultRouterObjectsLinkSearch
+    .parameter(0)
+    .toEqualTypeOf<{} | { page: number } | { page?: number }>()
+
   routerAlwaysTrailingSlashesLinkSearch
     .parameter(0)
     .toEqualTypeOf<{} | { page: number } | { page?: number }>()
@@ -1765,6 +2203,12 @@ test('when navigating to a route with optional search params', () => {
     '/invoices/$invoiceId/details/$detailId'
   >
 
+  const DefaultRouterObjectsLink = Link<
+    DefaultRouterObjects,
+    string,
+    '/invoices/$invoiceId/details/$detailId'
+  >
+
   const RouterAlwaysTrailingSlashesLink = Link<
     RouterAlwaysTrailingSlashes,
     string,
@@ -1788,6 +2232,10 @@ test('when navigating to a route with optional search params', () => {
     .parameter(0)
     .toHaveProperty('search')
 
+  const defaultRouterObjectsLinkSearch = expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toHaveProperty('search')
+
   const routerAlwaysTrailingSlashesLinkSearch = expectTypeOf(
     RouterAlwaysTrailingSlashesLink,
   )
@@ -1810,6 +2258,10 @@ test('when navigating to a route with optional search params', () => {
     .parameter(0)
     .not.toMatchTypeOf<{ search: unknown }>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .not.toMatchTypeOf<{ search: unknown }>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .not.toMatchTypeOf<{ search: unknown }>()
@@ -1826,6 +2278,10 @@ test('when navigating to a route with optional search params', () => {
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ page?: number | undefined } | undefined>()
 
+  defaultRouterObjectsLinkSearch
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ page?: number | undefined } | undefined>()
+
   routerAlwaysTrailingSlashesLinkSearch
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ page?: number | undefined } | undefined>()
@@ -1840,6 +2296,8 @@ test('when navigating to a route with optional search params', () => {
 
   defaultRouterLinkSearch.returns.toEqualTypeOf<{ page?: number }>()
 
+  defaultRouterObjectsLinkSearch.returns.toEqualTypeOf<{ page?: number }>()
+
   routerAlwaysTrailingSlashesLinkSearch.returns.toEqualTypeOf<{
     page?: number
   }>()
@@ -1856,6 +2314,10 @@ test('when navigating to a route with optional search params', () => {
     .parameter(0)
     .toEqualTypeOf<{} | { page: number } | { page?: number }>()
 
+  defaultRouterObjectsLinkSearch
+    .parameter(0)
+    .toEqualTypeOf<{} | { page: number } | { page?: number }>()
+
   routerAlwaysTrailingSlashesLinkSearch
     .parameter(0)
     .toEqualTypeOf<{} | { page: number } | { page?: number }>()
@@ -1876,6 +2338,12 @@ test('when navigating from a route with no search params to a route with search
     './$invoiceId/edit'
   >
 
+  const DefaultRouterObjectsLink = Link<
+    DefaultRouterObjects,
+    '/invoices/',
+    './$invoiceId/edit'
+  >
+
   const RouterAlwaysTrailingSlashesLink = Link<
     RouterAlwaysTrailingSlashes,
     '/invoices/',
@@ -1898,6 +2366,10 @@ test('when navigating from a route with no search params to a route with search
     .parameter(0)
     .toHaveProperty('search')
 
+  const defaultRouterObjectsLinkSearch = expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('search')
+
   const routerAlwaysTrailingSlashesLinkSearch = expectTypeOf(
     RouterAlwaysTrailingSlashesLink,
   )
@@ -1920,6 +2392,10 @@ test('when navigating from a route with no search params to a route with search
     .parameter(0)
     .toMatchTypeOf<{ search: unknown }>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .toMatchTypeOf<{ search: unknown }>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .toMatchTypeOf<{ search: unknown }>()
@@ -1936,6 +2412,10 @@ test('when navigating from a route with no search params to a route with search
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ page: number }>()
 
+  defaultRouterObjectsLinkSearch
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ page: number }>()
+
   routerAlwaysTrailingSlashesLinkSearch
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ page: number }>()
@@ -1946,6 +2426,8 @@ test('when navigating from a route with no search params to a route with search
 
   defaultRouterLinkSearch.returns.toEqualTypeOf<{ page: number }>()
 
+  defaultRouterObjectsLinkSearch.returns.toEqualTypeOf<{ page: number }>()
+
   routerAlwaysTrailingSlashesLinkSearch.returns.toEqualTypeOf<{
     page: number
   }>()
@@ -1960,6 +2442,8 @@ test('when navigating from a route with no search params to a route with search
 
   defaultRouterLinkSearch.parameter(0).toEqualTypeOf<{}>()
 
+  defaultRouterObjectsLinkSearch.parameter(0).toEqualTypeOf<{}>()
+
   routerAlwaysTrailingSlashesLinkSearch.parameter(0).toEqualTypeOf<{}>()
 
   routerNeverTrailingSlashesLinkSearch.parameter(0).toEqualTypeOf<{}>()
@@ -1974,6 +2458,12 @@ test('when navigating to a union of routes with search params', () => {
     '/invoices/$invoiceId/edit' | '/posts/$postId'
   >
 
+  const DefaultRouterObjectsLink = Link<
+    DefaultRouterObjects,
+    string,
+    '/invoices/$invoiceId/edit' | '/posts/$postId'
+  >
+
   const RouterAlwaysTrailingSlashesLink = Link<
     RouterAlwaysTrailingSlashes,
     string,
@@ -1999,6 +2489,10 @@ test('when navigating to a union of routes with search params', () => {
     .parameter(0)
     .toHaveProperty('search')
 
+  const defaultRouterObjectsLinkSearch = expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('search')
+
   const routerAlwaysTrailingSlashesSearch = expectTypeOf(
     RouterAlwaysTrailingSlashesLink,
   )
@@ -2021,6 +2515,10 @@ test('when navigating to a union of routes with search params', () => {
     .parameter(0)
     .not.toMatchTypeOf<{ search: unknown }>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .not.toMatchTypeOf<{ search: unknown }>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .not.toMatchTypeOf<{ search: unknown }>()
@@ -2037,6 +2535,10 @@ test('when navigating to a union of routes with search params', () => {
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ page: number } | {} | undefined>()
 
+  defaultRouterObjectsLinkSearch
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ page: number } | {} | undefined>()
+
   routerAlwaysTrailingSlashesSearch
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ page: number } | {} | undefined>()
@@ -2051,6 +2553,8 @@ test('when navigating to a union of routes with search params', () => {
 
   defaultRouterLinkSearch.returns.toEqualTypeOf<{ page: number } | {}>()
 
+  defaultRouterObjectsLinkSearch.returns.toEqualTypeOf<{ page: number } | {}>()
+
   routerAlwaysTrailingSlashesSearch.returns.toEqualTypeOf<
     { page: number } | {}
   >()
@@ -2065,6 +2569,8 @@ test('when navigating to a union of routes with search params', () => {
 
   defaultRouterLinkSearch.returns.toEqualTypeOf<{ page: number } | {}>()
 
+  defaultRouterObjectsLinkSearch.returns.toEqualTypeOf<{ page: number } | {}>()
+
   routerAlwaysTrailingSlashesSearch.returns.toEqualTypeOf<
     { page: number } | {}
   >()
@@ -2081,6 +2587,10 @@ test('when navigating to a union of routes with search params', () => {
     .parameter(0)
     .toEqualTypeOf<{} | { page: number } | { page?: number }>()
 
+  defaultRouterObjectsLinkSearch
+    .parameter(0)
+    .toEqualTypeOf<{} | { page: number } | { page?: number }>()
+
   routerAlwaysTrailingSlashesSearch
     .parameter(0)
     .toEqualTypeOf<{} | { page: number } | { page?: number }>()
@@ -2101,6 +2611,12 @@ test('when navigating to a union of routes with search params including the root
     '/' | '/invoices/$invoiceId/edit' | '/posts/$postId'
   >
 
+  const DefaultRouterObjectsLink = Link<
+    DefaultRouter,
+    string,
+    '/' | '/invoices/$invoiceId/edit' | '/posts/$postId'
+  >
+
   const RouterAlwaysTrailingSlashesLink = Link<
     RouterAlwaysTrailingSlashes,
     string,
@@ -2127,6 +2643,10 @@ test('when navigating to a union of routes with search params including the root
     .parameter(0)
     .toHaveProperty('search')
 
+  const defaultRouterObjectsSearch = expectTypeOf(DefaultRouterLink)
+    .parameter(0)
+    .toHaveProperty('search')
+
   const routerAlwaysTrailingSlashesLinkSearch = expectTypeOf(
     RouterAlwaysTrailingSlashesLink,
   )
@@ -2149,6 +2669,10 @@ test('when navigating to a union of routes with search params including the root
     .parameter(0)
     .not.toMatchTypeOf<{ search: unknown }>()
 
+  expectTypeOf(DefaultRouterObjectsLink)
+    .parameter(0)
+    .not.toMatchTypeOf<{ search: unknown }>()
+
   expectTypeOf(RouterAlwaysTrailingSlashesLink)
     .parameter(0)
     .not.toMatchTypeOf<{ search: unknown }>()
@@ -2165,6 +2689,10 @@ test('when navigating to a union of routes with search params including the root
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ page: number } | {} | undefined>()
 
+  defaultRouterObjectsSearch
+    .exclude<Function | boolean>()
+    .toEqualTypeOf<{ page: number } | {} | undefined>()
+
   routerAlwaysTrailingSlashesLinkSearch
     .exclude<Function | boolean>()
     .toEqualTypeOf<{ page: number } | {} | undefined>()
@@ -2179,6 +2707,8 @@ test('when navigating to a union of routes with search params including the root
 
   defaultRouterSearch.returns.toEqualTypeOf<{ page: number } | {}>()
 
+  defaultRouterObjectsSearch.returns.toEqualTypeOf<{ page: number } | {}>()
+
   routerAlwaysTrailingSlashesLinkSearch.returns.toEqualTypeOf<
     { page: number } | {}
   >()
@@ -2195,6 +2725,10 @@ test('when navigating to a union of routes with search params including the root
     .parameter(0)
     .toEqualTypeOf<{} | { page: number } | { page?: number }>()
 
+  defaultRouterObjectsSearch
+    .parameter(0)
+    .toEqualTypeOf<{} | { page: number } | { page?: number }>()
+
   routerAlwaysTrailingSlashesLinkSearch
     .parameter(0)
     .toEqualTypeOf<{} | { page: number } | { page?: number }>()
diff --git a/packages/react-router/tests/router.test.tsx b/packages/react-router/tests/router.test.tsx
index 51c0a02cf..6b1a44f5c 100644
--- a/packages/react-router/tests/router.test.tsx
+++ b/packages/react-router/tests/router.test.tsx
@@ -1,13 +1,13 @@
 import React from 'react'
-import { describe, it, expect, afterEach, vi } from 'vitest'
+import { afterEach, describe, expect, it, vi } from 'vitest'
 import { cleanup, render, waitFor } from '@testing-library/react'
 import {
+  type RouterHistory,
+  RouterProvider,
+  createMemoryHistory,
   createRootRoute,
   createRoute,
   createRouter,
-  createMemoryHistory,
-  RouterProvider,
-  type RouterHistory,
 } from '../src'
 
 afterEach(() => {
@@ -35,11 +35,46 @@ function createTestRouter(initialHistory?: RouterHistory) {
     getParentRoute: () => rootRoute,
     path: '$',
   })
+
   const routeTree = rootRoute.addChildren([
     indexRoute,
     postsRoute.addChildren([postIdRoute]),
     topLevelSplatRoute,
   ])
+
+  const router = createRouter({ routeTree, history })
+
+  return {
+    router,
+    routes: { indexRoute, postsRoute, postIdRoute, topLevelSplatRoute },
+  }
+}
+
+function createTestRouterWithObjects(initialHistory?: RouterHistory) {
+  const history =
+    initialHistory ?? createMemoryHistory({ initialEntries: ['/'] })
+
+  const rootRoute = createRootRoute({})
+  const indexRoute = createRoute({ getParentRoute: () => rootRoute, path: '/' })
+  const postsRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/posts',
+  })
+  const postIdRoute = createRoute({
+    getParentRoute: () => postsRoute,
+    path: '/$slug',
+  })
+  const topLevelSplatRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '$',
+  })
+
+  const routeTree = rootRoute.addChildren({
+    indexRoute,
+    postsRoute: postsRoute.addChildren({ postIdRoute }),
+    topLevelSplatRoute,
+  })
+
   const router = createRouter({ routeTree, history })
 
   return {
@@ -111,6 +146,22 @@ describe('encoding: path params for /posts/$slug', () => {
     }
 
     expect((match.params as unknown as any).slug).toBe('tanner')
+
+    const routerWithObjects = createTestRouterWithObjects(
+      createMemoryHistory({ initialEntries: ['/posts/tanner'] }),
+    )
+
+    await routerWithObjects.router.load()
+
+    const matchWithObjects = routerWithObjects.router.state.matches.find(
+      (r) => r.routeId === routes.postIdRoute.id,
+    )
+
+    if (!matchWithObjects) {
+      throw new Error('No match found')
+    }
+
+    expect((matchWithObjects.params as unknown as any).slug).toBe('tanner')
   })
 
   it('params.slug for the matched route, should be "🚀"', async () => {
@@ -129,6 +180,22 @@ describe('encoding: path params for /posts/$slug', () => {
     }
 
     expect((match.params as unknown as any).slug).toBe('🚀')
+
+    const routerWithObjects = createTestRouterWithObjects(
+      createMemoryHistory({ initialEntries: ['/posts/🚀'] }),
+    )
+
+    await routerWithObjects.router.load()
+
+    const matchWithObjects = routerWithObjects.router.state.matches.find(
+      (r) => r.routeId === routes.postIdRoute.id,
+    )
+
+    if (!matchWithObjects) {
+      throw new Error('No match found')
+    }
+
+    expect((matchWithObjects.params as unknown as any).slug).toBe('🚀')
   })
 
   it('params.slug for the matched route, should be "🚀" instead of it being "%F0%9F%9A%80"', async () => {
@@ -147,6 +214,22 @@ describe('encoding: path params for /posts/$slug', () => {
     }
 
     expect((match.params as unknown as any).slug).toBe('🚀')
+
+    const routerWithObjects = createTestRouterWithObjects(
+      createMemoryHistory({ initialEntries: ['/posts/%F0%9F%9A%80'] }),
+    )
+
+    await routerWithObjects.router.load()
+
+    const matchWithObjects = routerWithObjects.router.state.matches.find(
+      (r) => r.routeId === routes.postIdRoute.id,
+    )
+
+    if (!matchWithObjects) {
+      throw new Error('No match found')
+    }
+
+    expect((matchWithObjects.params as unknown as any).slug).toBe('🚀')
   })
 
   it('params.slug for the matched route, should be "framework/react/guide/file-based-routing tanstack" instead of it being "framework%2Freact%2Fguide%2Ffile-based-routing%20tanstack"', async () => {
@@ -171,6 +254,28 @@ describe('encoding: path params for /posts/$slug', () => {
     expect((match.params as unknown as any).slug).toBe(
       'framework/react/guide/file-based-routing tanstack',
     )
+
+    const routerWithObjects = createTestRouterWithObjects(
+      createMemoryHistory({
+        initialEntries: [
+          '/posts/framework%2Freact%2Fguide%2Ffile-based-routing%20tanstack',
+        ],
+      }),
+    )
+
+    await routerWithObjects.router.load()
+
+    const matchWithObjects = routerWithObjects.router.state.matches.find(
+      (r) => r.routeId === routes.postIdRoute.id,
+    )
+
+    if (!matchWithObjects) {
+      throw new Error('No match found')
+    }
+
+    expect((matchWithObjects.params as unknown as any).slug).toBe(
+      'framework/react/guide/file-based-routing tanstack',
+    )
   })
 })
 
diff --git a/packages/router-generator/tests/generator/append-and-prepend/routeTree.expected.ts b/packages/router-generator/tests/generator/append-and-prepend/routeTree.expected.ts
index d4de97299..44e8a7828 100644
--- a/packages/router-generator/tests/generator/append-and-prepend/routeTree.expected.ts
+++ b/packages/router-generator/tests/generator/append-and-prepend/routeTree.expected.ts
@@ -21,6 +21,9 @@ const IndexRoute = IndexImport.update({
 declare module '@tanstack/react-router' {
   interface FileRoutesByPath {
     '/': {
+      id: '/'
+      path: '/'
+      fullPath: '/'
       preLoaderRoute: typeof IndexImport
       parentRoute: typeof rootRoute
     }
@@ -29,7 +32,7 @@ declare module '@tanstack/react-router' {
 
 // Create and export the route tree
 
-export const routeTree = rootRoute.addChildren([IndexRoute])
+export const routeTree = rootRoute.addChildren({ IndexRoute })
 
 // append1
 
diff --git a/packages/router-generator/tests/generator/flat/routeTree.expected.ts b/packages/router-generator/tests/generator/flat/routeTree.expected.ts
index bff4e4fe4..a1580a19b 100644
--- a/packages/router-generator/tests/generator/flat/routeTree.expected.ts
+++ b/packages/router-generator/tests/generator/flat/routeTree.expected.ts
@@ -73,38 +73,65 @@ const PostsPostIdDeepRoute = PostsPostIdDeepImport.update({
 declare module '@tanstack/react-router' {
   interface FileRoutesByPath {
     '/': {
+      id: '/'
+      path: '/'
+      fullPath: '/'
       preLoaderRoute: typeof IndexImport
       parentRoute: typeof rootRoute
     }
     '/blog': {
+      id: '/blog'
+      path: '/blog'
+      fullPath: '/blog'
       preLoaderRoute: typeof BlogRouteImport
       parentRoute: typeof rootRoute
     }
     '/posts': {
+      id: '/posts'
+      path: '/posts'
+      fullPath: '/posts'
       preLoaderRoute: typeof PostsRouteImport
       parentRoute: typeof rootRoute
     }
     '/blog/stats': {
+      id: '/blog/stats'
+      path: '/blog/stats'
+      fullPath: '/blog/stats'
       preLoaderRoute: typeof BlogStatsImport
       parentRoute: typeof rootRoute
     }
     '/blog/': {
+      id: '/blog/'
+      path: '/'
+      fullPath: '/blog/'
       preLoaderRoute: typeof BlogIndexImport
       parentRoute: typeof BlogRouteImport
     }
     '/posts/': {
+      id: '/posts/'
+      path: '/'
+      fullPath: '/posts/'
       preLoaderRoute: typeof PostsIndexImport
       parentRoute: typeof PostsRouteImport
     }
     '/posts/$postId/deep': {
+      id: '/posts/$postId/deep'
+      path: '/$postId/deep'
+      fullPath: '/posts/$postId/deep'
       preLoaderRoute: typeof PostsPostIdDeepImport
       parentRoute: typeof PostsRouteImport
     }
     '/blog/$slug/': {
+      id: '/blog/$slug/'
+      path: '/$slug/'
+      fullPath: '/blog/$slug/'
       preLoaderRoute: typeof BlogSlugIndexImport
       parentRoute: typeof BlogRouteImport
     }
     '/posts/$postId/': {
+      id: '/posts/$postId/'
+      path: '/$postId/'
+      fullPath: '/posts/$postId/'
       preLoaderRoute: typeof PostsPostIdIndexImport
       parentRoute: typeof PostsRouteImport
     }
@@ -113,15 +140,18 @@ declare module '@tanstack/react-router' {
 
 // Create and export the route tree
 
-export const routeTree = rootRoute.addChildren([
+export const routeTree = rootRoute.addChildren({
   IndexRoute,
-  BlogRouteRoute.addChildren([BlogIndexRoute, BlogSlugIndexRoute]),
-  PostsRouteRoute.addChildren([
+  BlogRouteRoute: BlogRouteRoute.addChildren({
+    BlogIndexRoute,
+    BlogSlugIndexRoute,
+  }),
+  PostsRouteRoute: PostsRouteRoute.addChildren({
     PostsIndexRoute,
     PostsPostIdDeepRoute,
     PostsPostIdIndexRoute,
-  ]),
+  }),
   BlogStatsRoute,
-])
+})
 
 /* prettier-ignore-end */
diff --git a/packages/router-generator/tests/generator/nested-layouts/routeTree.expected.ts b/packages/router-generator/tests/generator/nested-layouts/routeTree.expected.ts
index 89a65ec65..fcd30315b 100644
--- a/packages/router-generator/tests/generator/nested-layouts/routeTree.expected.ts
+++ b/packages/router-generator/tests/generator/nested-layouts/routeTree.expected.ts
@@ -23,6 +23,7 @@ import { Route as JestedLayoutB4Import } from './routes/jested/_layout-b4'
 import { Route as JestedLayoutB3Import } from './routes/jested/_layout-b3'
 import { Route as LayoutA2BarImport } from './routes/_layout-a2/bar'
 import { Route as LayoutA1FooImport } from './routes/_layout-a1/foo'
+import { Route as folderInFolderImport } from './routes/(folder)/in-folder'
 import { Route as NestedLayoutB1IndexImport } from './routes/nested/_layout-b1/index'
 import { Route as JestedLayoutB3IndexImport } from './routes/jested/_layout-b3/index'
 import { Route as NestedLayoutB2FooImport } from './routes/nested/_layout-b2/foo'
@@ -93,6 +94,11 @@ const LayoutA1FooRoute = LayoutA1FooImport.update({
   getParentRoute: () => LayoutA1Route,
 } as any)
 
+const folderInFolderRoute = folderInFolderImport.update({
+  path: '/in-folder',
+  getParentRoute: () => rootRoute,
+} as any)
+
 const NestedLayoutB1IndexRoute = NestedLayoutB1IndexImport.update({
   path: '/',
   getParentRoute: () => NestedLayoutB1Route,
@@ -138,78 +144,142 @@ const JestedLayoutB3LayoutC2BarRoute = JestedLayoutB3LayoutC2BarImport.update({
 declare module '@tanstack/react-router' {
   interface FileRoutesByPath {
     '/': {
+      id: '/'
+      path: '/'
+      fullPath: '/'
       preLoaderRoute: typeof IndexImport
       parentRoute: typeof rootRoute
     }
     '/jested': {
+      id: '/jested'
+      path: '/jested'
+      fullPath: '/jested'
       preLoaderRoute: typeof JestedRouteImport
       parentRoute: typeof rootRoute
     }
     '/_layout-a1': {
+      id: '/_layout-a1'
+      path: ''
+      fullPath: ''
       preLoaderRoute: typeof LayoutA1Import
       parentRoute: typeof rootRoute
     }
     '/_layout-a2': {
+      id: '/_layout-a2'
+      path: ''
+      fullPath: ''
       preLoaderRoute: typeof LayoutA2Import
       parentRoute: typeof rootRoute
     }
+    '/(folder)/in-folder': {
+      id: '/in-folder'
+      path: '/in-folder'
+      fullPath: '/in-folder'
+      preLoaderRoute: typeof folderInFolderImport
+      parentRoute: typeof rootRoute
+    }
     '/_layout-a1/foo': {
+      id: '/_layout-a1/foo'
+      path: '/foo'
+      fullPath: '/foo'
       preLoaderRoute: typeof LayoutA1FooImport
       parentRoute: typeof LayoutA1Import
     }
     '/_layout-a2/bar': {
+      id: '/_layout-a2/bar'
+      path: '/bar'
+      fullPath: '/bar'
       preLoaderRoute: typeof LayoutA2BarImport
       parentRoute: typeof LayoutA2Import
     }
     '/jested/_layout-b3': {
+      id: '/jested/_layout-b3'
+      path: ''
+      fullPath: '/jested'
       preLoaderRoute: typeof JestedLayoutB3Import
       parentRoute: typeof JestedRouteImport
     }
     '/jested/_layout-b4': {
+      id: '/jested/_layout-b4'
+      path: ''
+      fullPath: '/jested'
       preLoaderRoute: typeof JestedLayoutB4Import
       parentRoute: typeof JestedRouteImport
     }
     '/nested': {
+      id: '/nested'
+      path: '/nested'
+      fullPath: '/nested'
       preLoaderRoute: typeof NestedImport
       parentRoute: typeof rootRoute
     }
     '/nested/_layout-b1': {
+      id: '/nested/_layout-b1'
+      path: '/nested'
+      fullPath: '/nested'
       preLoaderRoute: typeof NestedLayoutB1Import
       parentRoute: typeof NestedRoute
     }
     '/nested/_layout-b2': {
+      id: '/nested/_layout-b2'
+      path: ''
+      fullPath: '/nested'
       preLoaderRoute: typeof NestedLayoutB2Import
       parentRoute: typeof NestedImport
     }
     '/jested/_layout-b3/_layout-c2': {
+      id: '/jested/_layout-b3/_layout-c2'
+      path: ''
+      fullPath: '/jested'
       preLoaderRoute: typeof JestedLayoutB3LayoutC2Import
       parentRoute: typeof JestedLayoutB3Import
     }
     '/jested/_layout-b4/foo': {
+      id: '/jested/_layout-b4/foo'
+      path: '/foo'
+      fullPath: '/jested/foo'
       preLoaderRoute: typeof JestedLayoutB4FooImport
       parentRoute: typeof JestedLayoutB4Import
     }
     '/nested/_layout-b1/_layout-c1': {
+      id: '/nested/_layout-b1/_layout-c1'
+      path: ''
+      fullPath: '/nested'
       preLoaderRoute: typeof NestedLayoutB1LayoutC1Import
       parentRoute: typeof NestedLayoutB1Import
     }
     '/nested/_layout-b2/foo': {
+      id: '/nested/_layout-b2/foo'
+      path: '/foo'
+      fullPath: '/nested/foo'
       preLoaderRoute: typeof NestedLayoutB2FooImport
       parentRoute: typeof NestedLayoutB2Import
     }
     '/jested/_layout-b3/': {
+      id: '/jested/_layout-b3/'
+      path: '/'
+      fullPath: '/jested/'
       preLoaderRoute: typeof JestedLayoutB3IndexImport
       parentRoute: typeof JestedLayoutB3Import
     }
     '/nested/_layout-b1/': {
+      id: '/nested/_layout-b1/'
+      path: '/'
+      fullPath: '/nested/'
       preLoaderRoute: typeof NestedLayoutB1IndexImport
       parentRoute: typeof NestedLayoutB1Import
     }
     '/jested/_layout-b3/_layout-c2/bar': {
+      id: '/jested/_layout-b3/_layout-c2/bar'
+      path: '/bar'
+      fullPath: '/jested/bar'
       preLoaderRoute: typeof JestedLayoutB3LayoutC2BarImport
       parentRoute: typeof JestedLayoutB3LayoutC2Import
     }
     '/nested/_layout-b1/_layout-c1/bar': {
+      id: '/nested/_layout-b1/_layout-c1/bar'
+      path: '/bar'
+      fullPath: '/nested/bar'
       preLoaderRoute: typeof NestedLayoutB1LayoutC1BarImport
       parentRoute: typeof NestedLayoutB1LayoutC1Import
     }
@@ -218,24 +288,33 @@ declare module '@tanstack/react-router' {
 
 // Create and export the route tree
 
-export const routeTree = rootRoute.addChildren([
+export const routeTree = rootRoute.addChildren({
   IndexRoute,
-  JestedRouteRoute.addChildren([
-    JestedLayoutB3Route.addChildren([
-      JestedLayoutB3LayoutC2Route.addChildren([JestedLayoutB3LayoutC2BarRoute]),
+  JestedRouteRoute: JestedRouteRoute.addChildren({
+    JestedLayoutB3Route: JestedLayoutB3Route.addChildren({
+      JestedLayoutB3LayoutC2Route: JestedLayoutB3LayoutC2Route.addChildren({
+        JestedLayoutB3LayoutC2BarRoute,
+      }),
       JestedLayoutB3IndexRoute,
-    ]),
-    JestedLayoutB4Route.addChildren([JestedLayoutB4FooRoute]),
-  ]),
-  LayoutA1Route.addChildren([LayoutA1FooRoute]),
-  LayoutA2Route.addChildren([LayoutA2BarRoute]),
-  NestedRoute.addChildren([
-    NestedLayoutB1Route.addChildren([
-      NestedLayoutB1LayoutC1Route.addChildren([NestedLayoutB1LayoutC1BarRoute]),
+    }),
+    JestedLayoutB4Route: JestedLayoutB4Route.addChildren({
+      JestedLayoutB4FooRoute,
+    }),
+  }),
+  LayoutA1Route: LayoutA1Route.addChildren({ LayoutA1FooRoute }),
+  LayoutA2Route: LayoutA2Route.addChildren({ LayoutA2BarRoute }),
+  folderInFolderRoute,
+  NestedRoute: NestedRoute.addChildren({
+    NestedLayoutB1Route: NestedLayoutB1Route.addChildren({
+      NestedLayoutB1LayoutC1Route: NestedLayoutB1LayoutC1Route.addChildren({
+        NestedLayoutB1LayoutC1BarRoute,
+      }),
       NestedLayoutB1IndexRoute,
-    ]),
-    NestedLayoutB2Route.addChildren([NestedLayoutB2FooRoute]),
-  ]),
-])
+    }),
+    NestedLayoutB2Route: NestedLayoutB2Route.addChildren({
+      NestedLayoutB2FooRoute,
+    }),
+  }),
+})
 
 /* prettier-ignore-end */
diff --git a/packages/router-generator/tests/generator/nested-layouts/routes/(folder)/in-folder.tsx b/packages/router-generator/tests/generator/nested-layouts/routes/(folder)/in-folder.tsx
new file mode 100644
index 000000000..ab504e42e
--- /dev/null
+++ b/packages/router-generator/tests/generator/nested-layouts/routes/(folder)/in-folder.tsx
@@ -0,0 +1,1 @@
+/** */
diff --git a/packages/router-generator/tests/generator/nested/routeTree.expected.ts b/packages/router-generator/tests/generator/nested/routeTree.expected.ts
index d248cae05..028472986 100644
--- a/packages/router-generator/tests/generator/nested/routeTree.expected.ts
+++ b/packages/router-generator/tests/generator/nested/routeTree.expected.ts
@@ -73,38 +73,65 @@ const PostsPostIdDeepRoute = PostsPostIdDeepImport.update({
 declare module '@tanstack/react-router' {
   interface FileRoutesByPath {
     '/': {
+      id: '/'
+      path: '/'
+      fullPath: '/'
       preLoaderRoute: typeof IndexImport
       parentRoute: typeof rootRoute
     }
     '/blog': {
+      id: '/blog'
+      path: '/blog'
+      fullPath: '/blog'
       preLoaderRoute: typeof BlogRouteImport
       parentRoute: typeof rootRoute
     }
     '/posts': {
+      id: '/posts'
+      path: '/posts'
+      fullPath: '/posts'
       preLoaderRoute: typeof PostsRouteImport
       parentRoute: typeof rootRoute
     }
     '/blog/$slug': {
+      id: '/blog/$slug'
+      path: '/$slug'
+      fullPath: '/blog/$slug'
       preLoaderRoute: typeof BlogSlugImport
       parentRoute: typeof BlogRouteImport
     }
     '/blog/stats': {
+      id: '/blog/stats'
+      path: '/blog/stats'
+      fullPath: '/blog/stats'
       preLoaderRoute: typeof BlogStatsImport
       parentRoute: typeof rootRoute
     }
     '/blog/': {
+      id: '/blog/'
+      path: '/'
+      fullPath: '/blog/'
       preLoaderRoute: typeof BlogIndexImport
       parentRoute: typeof BlogRouteImport
     }
     '/posts/': {
+      id: '/posts/'
+      path: '/'
+      fullPath: '/posts/'
       preLoaderRoute: typeof PostsIndexImport
       parentRoute: typeof PostsRouteImport
     }
     '/posts/$postId/deep': {
+      id: '/posts/$postId/deep'
+      path: '/$postId/deep'
+      fullPath: '/posts/$postId/deep'
       preLoaderRoute: typeof PostsPostIdDeepImport
       parentRoute: typeof PostsRouteImport
     }
     '/posts/$postId/': {
+      id: '/posts/$postId/'
+      path: '/$postId/'
+      fullPath: '/posts/$postId/'
       preLoaderRoute: typeof PostsPostIdIndexImport
       parentRoute: typeof PostsRouteImport
     }
@@ -113,15 +140,15 @@ declare module '@tanstack/react-router' {
 
 // Create and export the route tree
 
-export const routeTree = rootRoute.addChildren([
+export const routeTree = rootRoute.addChildren({
   IndexRoute,
-  BlogRouteRoute.addChildren([BlogSlugRoute, BlogIndexRoute]),
-  PostsRouteRoute.addChildren([
+  BlogRouteRoute: BlogRouteRoute.addChildren({ BlogSlugRoute, BlogIndexRoute }),
+  PostsRouteRoute: PostsRouteRoute.addChildren({
     PostsIndexRoute,
     PostsPostIdDeepRoute,
     PostsPostIdIndexRoute,
-  ]),
+  }),
   BlogStatsRoute,
-])
+})
 
 /* prettier-ignore-end */
diff --git a/packages/router-generator/tests/generator/single-level/routeTree.expected.ts b/packages/router-generator/tests/generator/single-level/routeTree.expected.ts
index dc8fddf5f..62c8f0820 100644
--- a/packages/router-generator/tests/generator/single-level/routeTree.expected.ts
+++ b/packages/router-generator/tests/generator/single-level/routeTree.expected.ts
@@ -31,10 +31,16 @@ const IndexRoute = IndexImport.update({
 declare module '@tanstack/react-router' {
   interface FileRoutesByPath {
     '/': {
+      id: '/'
+      path: '/'
+      fullPath: '/'
       preLoaderRoute: typeof IndexImport
       parentRoute: typeof rootRoute
     }
     '/posts': {
+      id: '/posts'
+      path: '/posts'
+      fullPath: '/posts'
       preLoaderRoute: typeof PostsImport
       parentRoute: typeof rootRoute
     }
@@ -43,6 +49,6 @@ declare module '@tanstack/react-router' {
 
 // Create and export the route tree
 
-export const routeTree = rootRoute.addChildren([IndexRoute, PostsRoute])
+export const routeTree = rootRoute.addChildren({ IndexRoute, PostsRoute })
 
 /* prettier-ignore-end */