@tanstack/react-router 1.40.0 → 1.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/cjs/CatchBoundary.d.cts +2 -2
  2. package/dist/cjs/Match.cjs +238 -0
  3. package/dist/cjs/Match.cjs.map +1 -0
  4. package/dist/cjs/Match.d.cts +5 -0
  5. package/dist/cjs/Matches.cjs +8 -249
  6. package/dist/cjs/Matches.cjs.map +1 -1
  7. package/dist/cjs/Matches.d.cts +2 -11
  8. package/dist/cjs/RouterProvider.cjs.map +1 -1
  9. package/dist/cjs/RouterProvider.d.cts +2 -2
  10. package/dist/cjs/SafeFragment.cjs +8 -0
  11. package/dist/cjs/SafeFragment.cjs.map +1 -0
  12. package/dist/cjs/SafeFragment.d.cts +1 -0
  13. package/dist/cjs/ScriptOnce.cjs +28 -0
  14. package/dist/cjs/ScriptOnce.cjs.map +1 -0
  15. package/dist/cjs/ScriptOnce.d.cts +5 -0
  16. package/dist/cjs/Transitioner.cjs +2 -1
  17. package/dist/cjs/Transitioner.cjs.map +1 -1
  18. package/dist/cjs/awaited.cjs +14 -71
  19. package/dist/cjs/awaited.cjs.map +1 -1
  20. package/dist/cjs/awaited.d.cts +3 -6
  21. package/dist/cjs/defer.cjs +7 -13
  22. package/dist/cjs/defer.cjs.map +1 -1
  23. package/dist/cjs/defer.d.cts +2 -6
  24. package/dist/cjs/fileRoute.d.cts +9 -1
  25. package/dist/cjs/index.cjs +11 -7
  26. package/dist/cjs/index.cjs.map +1 -1
  27. package/dist/cjs/index.d.cts +9 -4
  28. package/dist/cjs/isServerSideError.cjs +22 -0
  29. package/dist/cjs/isServerSideError.cjs.map +1 -0
  30. package/dist/cjs/isServerSideError.d.cts +5 -0
  31. package/dist/cjs/link.cjs +1 -0
  32. package/dist/cjs/link.cjs.map +1 -1
  33. package/dist/cjs/matchContext.cjs +23 -0
  34. package/dist/cjs/matchContext.cjs.map +1 -0
  35. package/dist/cjs/matchContext.d.cts +2 -0
  36. package/dist/cjs/not-found.cjs.map +1 -1
  37. package/dist/cjs/not-found.d.cts +2 -2
  38. package/dist/cjs/qss.cjs.map +1 -1
  39. package/dist/cjs/qss.d.cts +1 -1
  40. package/dist/cjs/redirects.cjs.map +1 -1
  41. package/dist/cjs/renderRouteNotFound.cjs +22 -0
  42. package/dist/cjs/renderRouteNotFound.cjs.map +1 -0
  43. package/dist/cjs/renderRouteNotFound.d.cts +4 -0
  44. package/dist/cjs/route.cjs.map +1 -1
  45. package/dist/cjs/route.d.cts +37 -31
  46. package/dist/cjs/router.cjs +35 -25
  47. package/dist/cjs/router.cjs.map +1 -1
  48. package/dist/cjs/router.d.cts +14 -9
  49. package/dist/cjs/useMatch.cjs +2 -2
  50. package/dist/cjs/useMatch.cjs.map +1 -1
  51. package/dist/cjs/utils.cjs +4 -3
  52. package/dist/cjs/utils.cjs.map +1 -1
  53. package/dist/cjs/utils.d.cts +3 -2
  54. package/dist/esm/CatchBoundary.d.ts +2 -2
  55. package/dist/esm/Match.d.ts +5 -0
  56. package/dist/esm/Match.js +221 -0
  57. package/dist/esm/Match.js.map +1 -0
  58. package/dist/esm/Matches.d.ts +2 -11
  59. package/dist/esm/Matches.js +5 -246
  60. package/dist/esm/Matches.js.map +1 -1
  61. package/dist/esm/RouterProvider.d.ts +2 -2
  62. package/dist/esm/RouterProvider.js.map +1 -1
  63. package/dist/esm/SafeFragment.d.ts +1 -0
  64. package/dist/esm/SafeFragment.js +8 -0
  65. package/dist/esm/SafeFragment.js.map +1 -0
  66. package/dist/esm/ScriptOnce.d.ts +5 -0
  67. package/dist/esm/ScriptOnce.js +28 -0
  68. package/dist/esm/ScriptOnce.js.map +1 -0
  69. package/dist/esm/Transitioner.js +2 -1
  70. package/dist/esm/Transitioner.js.map +1 -1
  71. package/dist/esm/awaited.d.ts +3 -6
  72. package/dist/esm/awaited.js +16 -73
  73. package/dist/esm/awaited.js.map +1 -1
  74. package/dist/esm/defer.d.ts +2 -6
  75. package/dist/esm/defer.js +8 -14
  76. package/dist/esm/defer.js.map +1 -1
  77. package/dist/esm/fileRoute.d.ts +9 -1
  78. package/dist/esm/index.d.ts +9 -4
  79. package/dist/esm/index.js +9 -5
  80. package/dist/esm/index.js.map +1 -1
  81. package/dist/esm/isServerSideError.d.ts +5 -0
  82. package/dist/esm/isServerSideError.js +22 -0
  83. package/dist/esm/isServerSideError.js.map +1 -0
  84. package/dist/esm/link.js +1 -0
  85. package/dist/esm/link.js.map +1 -1
  86. package/dist/esm/matchContext.d.ts +2 -0
  87. package/dist/esm/matchContext.js +6 -0
  88. package/dist/esm/matchContext.js.map +1 -0
  89. package/dist/esm/not-found.d.ts +2 -2
  90. package/dist/esm/not-found.js.map +1 -1
  91. package/dist/esm/qss.d.ts +1 -1
  92. package/dist/esm/qss.js.map +1 -1
  93. package/dist/esm/redirects.js.map +1 -1
  94. package/dist/esm/renderRouteNotFound.d.ts +4 -0
  95. package/dist/esm/renderRouteNotFound.js +22 -0
  96. package/dist/esm/renderRouteNotFound.js.map +1 -0
  97. package/dist/esm/route.d.ts +37 -31
  98. package/dist/esm/route.js.map +1 -1
  99. package/dist/esm/router.d.ts +14 -9
  100. package/dist/esm/router.js +35 -25
  101. package/dist/esm/router.js.map +1 -1
  102. package/dist/esm/useMatch.js +1 -1
  103. package/dist/esm/useMatch.js.map +1 -1
  104. package/dist/esm/utils.d.ts +3 -2
  105. package/dist/esm/utils.js +4 -3
  106. package/dist/esm/utils.js.map +1 -1
  107. package/package.json +4 -4
  108. package/src/Match.tsx +296 -0
  109. package/src/Matches.tsx +4 -333
  110. package/src/RouterProvider.tsx +1 -1
  111. package/src/SafeFragment.tsx +5 -0
  112. package/src/ScriptOnce.tsx +27 -0
  113. package/src/Transitioner.tsx +1 -1
  114. package/src/awaited.tsx +17 -89
  115. package/src/defer.ts +9 -26
  116. package/src/index.tsx +7 -16
  117. package/src/isServerSideError.tsx +23 -0
  118. package/src/link.tsx +2 -0
  119. package/src/matchContext.tsx +3 -0
  120. package/src/not-found.tsx +1 -1
  121. package/src/qss.ts +5 -6
  122. package/src/redirects.ts +0 -1
  123. package/src/renderRouteNotFound.tsx +28 -0
  124. package/src/route.ts +61 -65
  125. package/src/router.ts +61 -42
  126. package/src/useMatch.tsx +1 -1
  127. package/src/utils.ts +11 -9
package/src/index.tsx CHANGED
@@ -1,4 +1,3 @@
1
- //
2
1
  export {
3
2
  createHistory,
4
3
  createBrowserHistory,
@@ -12,13 +11,9 @@ export {
12
11
  } from '@tanstack/history'
13
12
  export { default as invariant } from 'tiny-invariant'
14
13
  export { default as warning } from 'tiny-warning'
15
- export { useAwaited, Await, type AwaitOptions, ScriptOnce } from './awaited'
16
- export {
17
- defer,
18
- isDehydratedDeferred,
19
- type DeferredPromiseState,
20
- type DeferredPromise,
21
- } from './defer'
14
+ export { useAwaited, Await, type AwaitOptions } from './awaited'
15
+ export { ScriptOnce } from './ScriptOnce'
16
+ export { defer, type DeferredPromiseState, type DeferredPromise } from './defer'
22
17
  export { CatchBoundary, ErrorComponent } from './CatchBoundary'
23
18
  export {
24
19
  FileRoute,
@@ -68,23 +63,21 @@ export {
68
63
  } from './link'
69
64
  export { type ParsedLocation } from './location'
70
65
  export {
71
- matchContext,
72
66
  Matches,
73
- Match,
74
- Outlet,
75
67
  useMatchRoute,
76
68
  MatchRoute,
77
69
  useMatches,
78
70
  useParentMatches,
79
71
  useChildMatches,
80
- isServerSideError,
81
- defaultDeserializeError,
82
72
  type RouteMatch,
83
73
  type AnyRouteMatch,
84
74
  type MatchRouteOptions,
85
75
  type UseMatchRouteOptions,
86
76
  type MakeMatchRouteOptions,
87
77
  } from './Matches'
78
+ export { matchContext } from './matchContext'
79
+ export { Match, Outlet } from './Match'
80
+ export { isServerSideError, defaultDeserializeError } from './isServerSideError'
88
81
  export { useMatch } from './useMatch'
89
82
  export { useLoaderDeps } from './useLoaderDeps'
90
83
  export { useLoaderData } from './useLoaderData'
@@ -132,16 +125,13 @@ export {
132
125
  type StaticDataRouteOption,
133
126
  type RoutePathOptionsIntersection,
134
127
  type RouteOptions,
135
- type ParamsFallback,
136
128
  type FileBaseRouteOptions,
137
129
  type BaseRouteOptions,
138
130
  type UpdatableRouteOptions,
139
131
  type UpdatableStaticRouteOption,
140
132
  type MetaDescriptor,
141
133
  type RouteLinkEntry,
142
- type ParseParamsOption,
143
134
  type ParseParamsFn,
144
- type ParseParamsObj,
145
135
  type SearchSchemaValidator,
146
136
  type SearchSchemaValidatorObj,
147
137
  type SearchSchemaValidatorFn,
@@ -268,3 +258,4 @@ export {
268
258
  type NotFoundError,
269
259
  } from './not-found'
270
260
  export { type Manifest, type RouterManagedTag } from './manifest'
261
+ export { createControlledPromise, type ControlledPromise } from './utils'
@@ -0,0 +1,23 @@
1
+ export function isServerSideError(error: unknown): error is {
2
+ __isServerError: true
3
+ data: Record<string, any>
4
+ } {
5
+ if (!(typeof error === 'object' && error && 'data' in error)) return false
6
+ if (!('__isServerError' in error && error.__isServerError)) return false
7
+ if (!(typeof error.data === 'object' && error.data)) return false
8
+
9
+ return error.__isServerError === true
10
+ }
11
+
12
+ export function defaultDeserializeError(serializedData: Record<string, any>) {
13
+ if ('name' in serializedData && 'message' in serializedData) {
14
+ const error = new Error(serializedData.message)
15
+ error.name = serializedData.name
16
+ if (process.env.NODE_ENV === 'development') {
17
+ error.stack = serializedData.stack
18
+ }
19
+ return error
20
+ }
21
+
22
+ return serializedData.data
23
+ }
package/src/link.tsx CHANGED
@@ -1,3 +1,5 @@
1
+ 'use client'
2
+
1
3
  import * as React from 'react'
2
4
  import { flushSync } from 'react-dom'
3
5
  import { useMatch } from './useMatch'
@@ -0,0 +1,3 @@
1
+ import * as React from 'react'
2
+
3
+ export const matchContext = React.createContext<string | undefined>(undefined)
package/src/not-found.tsx CHANGED
@@ -1,4 +1,4 @@
1
- // eslint-disable-next-line @typescript-eslint/consistent-type-imports
1
+ // eslint-disable-next-line ts/consistent-type-imports
2
2
  import * as React from 'react'
3
3
  import { CatchBoundary } from './CatchBoundary'
4
4
  import { useRouterState } from './useRouterState'
package/src/qss.ts CHANGED
@@ -1,5 +1,3 @@
1
- // @ts-nocheck
2
-
3
1
  // qss has been slightly modified and inlined here for our use cases (and compression's sake). We've included it as a hard dependency for MIT license attribution.
4
2
 
5
3
  /**
@@ -13,7 +11,7 @@
13
11
  * // Expected output: "token=foo&key=value"
14
12
  * ```
15
13
  */
16
- export function encode(obj, pfx?: string) {
14
+ export function encode(obj: any, pfx?: string) {
17
15
  let k,
18
16
  i,
19
17
  tmp,
@@ -44,7 +42,7 @@ export function encode(obj, pfx?: string) {
44
42
  * // Example input: toValue("123")
45
43
  * // Expected output: 123
46
44
  */
47
- function toValue(mix) {
45
+ function toValue(mix: any) {
48
46
  if (!mix) return ''
49
47
  const str = decodeURIComponent(mix)
50
48
  if (str === 'false') return false
@@ -61,9 +59,9 @@ function toValue(mix) {
61
59
  * // Example input: decode("token=foo&key=value")
62
60
  * // Expected output: { "token": "foo", "key": "value" }
63
61
  */
64
- export function decode(str, pfx?: string) {
62
+ export function decode(str: any, pfx?: string) {
65
63
  let tmp, k
66
- const out = {},
64
+ const out: any = {},
67
65
  arr = (pfx ? str.substr(pfx.length) : str).split('&')
68
66
 
69
67
  while ((tmp = arr.shift())) {
@@ -72,6 +70,7 @@ export function decode(str, pfx?: string) {
72
70
  k = tmp.slice(0, equalIndex)
73
71
  const value = tmp.slice(equalIndex + 1)
74
72
  if (out[k] !== void 0) {
73
+ // @ts-expect-error
75
74
  out[k] = [].concat(out[k], toValue(value))
76
75
  } else {
77
76
  out[k] = toValue(value)
package/src/redirects.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import type { NavigateOptions } from './link'
2
- import type { AnyRoute } from './route'
3
2
  import type { RoutePaths } from './routeInfo'
4
3
  import type { AnyRouter, RegisteredRouter } from './router'
5
4
  import type { PickAsRequired } from './utils'
@@ -0,0 +1,28 @@
1
+ import * as React from 'react'
2
+ import warning from 'tiny-warning'
3
+ import { DefaultGlobalNotFound } from './not-found'
4
+ import { type AnyRouter } from './router'
5
+ import { type AnyRoute } from './route'
6
+
7
+ export function renderRouteNotFound(
8
+ router: AnyRouter,
9
+ route: AnyRoute,
10
+ data: any,
11
+ ) {
12
+ if (!route.options.notFoundComponent) {
13
+ if (router.options.defaultNotFoundComponent) {
14
+ return <router.options.defaultNotFoundComponent data={data} />
15
+ }
16
+
17
+ if (process.env.NODE_ENV === 'development') {
18
+ warning(
19
+ route.options.notFoundComponent,
20
+ `A notFoundError was encountered on the route with ID "${route.id}", but a notFoundComponent option was not configured, nor was a router level defaultNotFoundComponent configured. Consider configuring at least one of these to avoid TanStack Router's overly generic defaultNotFoundComponent (<div>Not Found<div>)`,
21
+ )
22
+ }
23
+
24
+ return <DefaultGlobalNotFound />
25
+ }
26
+
27
+ return <route.options.notFoundComponent data={data} />
28
+ }
package/src/route.ts CHANGED
@@ -91,10 +91,36 @@ export type RouteOptions<
91
91
  NoInfer<TLoaderDeps>
92
92
  >
93
93
 
94
- export type ParamsFallback<
95
- TPath extends string,
96
- TParams,
97
- > = unknown extends TParams ? Record<ParsePathParams<TPath>, string> : TParams
94
+ export type ParseParamsFn<TPath extends string, TParams> = (
95
+ rawParams: Record<ParsePathParams<TPath>, string>,
96
+ ) => TParams extends Record<ParsePathParams<TPath>, any>
97
+ ? TParams
98
+ : Record<ParsePathParams<TPath>, any>
99
+
100
+ export type StringifyParamsFn<TPath extends string, TParams> = (
101
+ params: TParams,
102
+ ) => Record<ParsePathParams<TPath>, string>
103
+
104
+ export type ParamsOptions<TPath extends string, TParams> = {
105
+ params?: {
106
+ parse: ParseParamsFn<TPath, TParams>
107
+ stringify: StringifyParamsFn<TPath, TParams>
108
+ }
109
+
110
+ /**
111
+ @deprecated Use params.parse instead
112
+ */
113
+ parseParams?: ParseParamsFn<TPath, TParams>
114
+
115
+ /**
116
+ @deprecated Use params.stringify instead
117
+ */
118
+ stringifyParams?: StringifyParamsFn<TPath, TParams>
119
+ }
120
+
121
+ export interface FullSearchSchemaOption<TFullSearchSchema> {
122
+ search: TFullSearchSchema
123
+ }
98
124
 
99
125
  export type FileBaseRouteOptions<
100
126
  TPath extends string = string,
@@ -102,14 +128,16 @@ export type FileBaseRouteOptions<
102
128
  TSearchSchema = {},
103
129
  TFullSearchSchema = TSearchSchema,
104
130
  TParams = {},
105
- TAllParams = ParamsFallback<TPath, TParams>,
131
+ TAllParams = {},
106
132
  TRouteContextReturn = RouteContext,
107
133
  TParentAllContext = AnyContext,
108
134
  TAllContext = AnyContext,
109
135
  TLoaderDeps extends Record<string, any> = {},
110
136
  TLoaderDataReturn = {},
111
137
  > = {
112
- validateSearch?: SearchSchemaValidator<TSearchSchemaInput, TSearchSchema>
138
+ validateSearch?:
139
+ | ((input: TSearchSchemaInput) => TSearchSchema)
140
+ | { parse: (input: TSearchSchemaInput) => TSearchSchema }
113
141
  shouldReload?:
114
142
  | boolean
115
143
  | ((
@@ -119,36 +147,14 @@ export type FileBaseRouteOptions<
119
147
  // If an error is thrown here, the route's loader will not be called.
120
148
  // If thrown during a navigation, the navigation will be cancelled and the error will be passed to the `onError` function.
121
149
  // If thrown during a preload event, the error will be logged to the console.
122
- beforeLoad?: BeforeLoadFn<
123
- TFullSearchSchema,
124
- TAllParams,
125
- TRouteContextReturn,
126
- TParentAllContext
127
- >
128
- loaderDeps?: (opts: { search: TFullSearchSchema }) => TLoaderDeps
129
- loader?: RouteLoaderFn<
130
- TAllParams,
131
- NoInfer<TLoaderDeps>,
132
- NoInfer<TAllContext>,
133
- TLoaderDataReturn
134
- >
135
- } & (
136
- | {
137
- // Both or none
138
- parseParams?: (
139
- rawParams: IsAny<TPath, any, Record<ParsePathParams<TPath>, string>>,
140
- ) => TParams extends Record<ParsePathParams<TPath>, any>
141
- ? TParams
142
- : 'parseParams must return an object'
143
- stringifyParams?: (
144
- params: NoInfer<ParamsFallback<TPath, TParams>>,
145
- ) => Record<ParsePathParams<TPath>, string>
146
- }
147
- | {
148
- stringifyParams?: never
149
- parseParams?: never
150
- }
151
- )
150
+ beforeLoad?: (
151
+ ctx: BeforeLoadContext<TFullSearchSchema, TAllParams, TParentAllContext>,
152
+ ) => Promise<TRouteContextReturn> | TRouteContextReturn | void
153
+ loaderDeps?: (opts: FullSearchSchemaOption<TFullSearchSchema>) => TLoaderDeps
154
+ loader?: (
155
+ ctx: LoaderFnContext<TAllParams, TLoaderDeps, TAllContext>,
156
+ ) => TLoaderDataReturn | Promise<TLoaderDataReturn>
157
+ } & ParamsOptions<TPath, TParams>
152
158
 
153
159
  export type BaseRouteOptions<
154
160
  TParentRoute extends AnyRoute = AnyRoute,
@@ -158,7 +164,7 @@ export type BaseRouteOptions<
158
164
  TSearchSchema = {},
159
165
  TFullSearchSchema = TSearchSchema,
160
166
  TParams = {},
161
- TAllParams = ParamsFallback<TPath, TParams>,
167
+ TAllParams = {},
162
168
  TRouteContextReturn = RouteContext,
163
169
  TParentAllContext = AnyContext,
164
170
  TAllContext = AnyContext,
@@ -181,16 +187,14 @@ export type BaseRouteOptions<
181
187
  getParentRoute: () => TParentRoute
182
188
  }
183
189
 
184
- type BeforeLoadFn<
185
- in out TFullSearchSchema,
186
- in out TAllParams,
187
- TRouteContextReturn,
188
- in out TParentAllContext,
189
- > = (opts: {
190
- search: TFullSearchSchema
190
+ export interface BeforeLoadContext<
191
+ TFullSearchSchema,
192
+ TAllParams,
193
+ TParentAllContext,
194
+ > extends FullSearchSchemaOption<TFullSearchSchema> {
191
195
  abortController: AbortController
192
196
  preload: boolean
193
- params: TAllParams
197
+ params: Expand<TAllParams>
194
198
  context: TParentAllContext
195
199
  location: ParsedLocation
196
200
  /**
@@ -199,7 +203,7 @@ type BeforeLoadFn<
199
203
  navigate: NavigateFn
200
204
  buildLocation: BuildLocationFn
201
205
  cause: 'preload' | 'enter' | 'stay'
202
- }) => Promise<TRouteContextReturn> | TRouteContextReturn | void
206
+ }
203
207
 
204
208
  export type UpdatableRouteOptions<
205
209
  TRouteId,
@@ -286,21 +290,6 @@ type LdJsonValue = LdJsonPrimitive | LdJsonObject | LdJsonArray
286
290
 
287
291
  export type RouteLinkEntry = {}
288
292
 
289
- export type ParseParamsOption<TPath extends string, TParams> = ParseParamsFn<
290
- TPath,
291
- TParams
292
- >
293
-
294
- export type ParseParamsFn<TPath extends string, TParams> = (
295
- rawParams: IsAny<TPath, any, Record<ParsePathParams<TPath>, string>>,
296
- ) => TParams extends Record<ParsePathParams<TPath>, any>
297
- ? TParams
298
- : 'parseParams must return an object'
299
-
300
- export type ParseParamsObj<TPath extends string, TParams> = {
301
- parse?: ParseParamsFn<TPath, TParams>
302
- }
303
-
304
293
  // The parse type here allows a zod schema to be passed directly to the validator
305
294
  export type SearchSchemaValidator<TInput, TReturn> =
306
295
  | SearchSchemaValidatorObj<TInput, TReturn>
@@ -321,7 +310,7 @@ export type RouteLoaderFn<
321
310
  TLoaderData = undefined,
322
311
  > = (
323
312
  match: LoaderFnContext<TAllParams, TLoaderDeps, TAllContext>,
324
- ) => Promise<TLoaderData> | TLoaderData
313
+ ) => TLoaderData | Promise<TLoaderData>
325
314
 
326
315
  export interface LoaderFnContext<
327
316
  in out TAllParams = {},
@@ -330,7 +319,7 @@ export interface LoaderFnContext<
330
319
  > {
331
320
  abortController: AbortController
332
321
  preload: boolean
333
- params: TAllParams
322
+ params: Expand<TAllParams>
334
323
  deps: TLoaderDeps
335
324
  context: TAllContext
336
325
  location: ParsedLocation // Do not supply search schema here so as to demotivate people from trying to shortcut loaderDeps
@@ -706,7 +695,7 @@ export class Route<
706
695
 
707
696
  const isRoot = !options?.path && !options?.id
708
697
 
709
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
698
+ // eslint-disable-next-line ts/no-unnecessary-condition
710
699
  this.parentRoute = this.options?.getParentRoute?.()
711
700
 
712
701
  if (isRoot) {
@@ -992,6 +981,7 @@ export type RootRouteOptions<
992
981
  | 'caseSensitive'
993
982
  | 'parseParams'
994
983
  | 'stringifyParams'
984
+ | 'params'
995
985
  >
996
986
 
997
987
  export function createRootRouteWithContext<TRouterContext extends {}>() {
@@ -1126,6 +1116,7 @@ export function createRootRoute<
1126
1116
  | 'caseSensitive'
1127
1117
  | 'parseParams'
1128
1118
  | 'stringifyParams'
1119
+ | 'params'
1129
1120
  >,
1130
1121
  ) {
1131
1122
  return new RootRoute<
@@ -1294,7 +1285,12 @@ export class NotFoundRoute<
1294
1285
  TLoaderDataReturn,
1295
1286
  TLoaderData
1296
1287
  >,
1297
- 'caseSensitive' | 'parseParams' | 'stringifyParams' | 'path' | 'id'
1288
+ | 'caseSensitive'
1289
+ | 'parseParams'
1290
+ | 'stringifyParams'
1291
+ | 'path'
1292
+ | 'id'
1293
+ | 'params'
1298
1294
  >,
1299
1295
  ) {
1300
1296
  super({
package/src/router.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { createBrowserHistory, createMemoryHistory } from '@tanstack/history'
2
2
  import { Store } from '@tanstack/react-store'
3
3
  import invariant from 'tiny-invariant'
4
- import warning from 'tiny-warning'
5
4
  import { rootRouteId } from './root'
6
5
  import { defaultParseSearch, defaultStringifySearch } from './searchParams'
7
6
  import {
@@ -44,6 +43,7 @@ import type {
44
43
  LoaderFnContext,
45
44
  NotFoundRouteComponent,
46
45
  RootRoute,
46
+ RouteComponent,
47
47
  RouteMask,
48
48
  } from './route'
49
49
  import type {
@@ -57,22 +57,18 @@ import type {
57
57
  ControlledPromise,
58
58
  NonNullableUpdater,
59
59
  PickAsRequired,
60
- Timeout,
61
60
  Updater,
62
61
  } from './utils'
63
- import type { RouteComponent } from './route'
64
62
  import type {
65
63
  AnyRouteMatch,
66
64
  MakeRouteMatch,
67
65
  MatchRouteOptions,
68
- RouteMatch,
69
66
  } from './Matches'
70
67
  import type { ParsedLocation } from './location'
71
68
  import type { SearchParser, SearchSerializer } from './searchParams'
72
69
  import type {
73
70
  BuildLocationFn,
74
71
  CommitLocationOptions,
75
- InjectedHtmlEntry,
76
72
  NavigateFn,
77
73
  } from './RouterProvider'
78
74
 
@@ -82,13 +78,16 @@ import type { NotFoundError } from './not-found'
82
78
  import type { NavigateOptions, ResolveRelativePath, ToOptions } from './link'
83
79
  import type { NoInfer } from '@tanstack/react-store'
84
80
  import type { DeferredPromiseState } from './defer'
85
- import type { ErrorInfo } from 'react'
86
81
 
87
82
  //
88
83
 
89
84
  declare global {
90
85
  interface Window {
91
- __TSR_DEHYDRATED__?: { data: string }
86
+ __TSR__?: {
87
+ matches: Array<any>
88
+ cleanScripts: () => void
89
+ dehydrated?: any
90
+ }
92
91
  __TSR_ROUTER_CONTEXT__?: React.Context<Router<any, any>>
93
92
  }
94
93
  }
@@ -237,7 +236,7 @@ export interface RouterOptions<
237
236
  * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultoncatch-property)
238
237
  * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#handling-errors-with-routeoptionsoncatch)
239
238
  */
240
- defaultOnCatch?: (error: Error, errorInfo: ErrorInfo) => void
239
+ defaultOnCatch?: (error: Error, errorInfo: React.ErrorInfo) => void
241
240
  defaultViewTransition?: boolean
242
241
  /**
243
242
  * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/not-found-errors#the-notfoundmode-option)
@@ -508,6 +507,20 @@ export class Router<
508
507
  dehydratedData?: TDehydrated
509
508
  viewTransitionPromise?: ControlledPromise<true>
510
509
  manifest?: Manifest
510
+ AfterEachMatch?: (props: {
511
+ match: Pick<
512
+ AnyRouteMatch,
513
+ 'id' | 'status' | 'error' | 'loadPromise' | 'minPendingPromise'
514
+ >
515
+ matchIndex: number
516
+ }) => any
517
+ serializeLoaderData?: (
518
+ data: any,
519
+ ctx: {
520
+ router: AnyRouter
521
+ match: AnyRouteMatch
522
+ },
523
+ ) => any
511
524
 
512
525
  // Must build in constructor
513
526
  __store!: Store<RouterState<TRouteTree>>
@@ -603,7 +616,7 @@ export class Router<
603
616
  }
604
617
 
605
618
  if (
606
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
619
+ // eslint-disable-next-line ts/no-unnecessary-condition
607
620
  !this.history ||
608
621
  (this.options.history && this.options.history !== this.history)
609
622
  ) {
@@ -622,7 +635,7 @@ export class Router<
622
635
  this.buildRouteTree()
623
636
  }
624
637
 
625
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
638
+ // eslint-disable-next-line ts/no-unnecessary-condition
626
639
  if (!this.__store) {
627
640
  this.__store = new Store(getInitialRouterState(this.latestLocation), {
628
641
  onUpdate: () => {
@@ -921,9 +934,12 @@ export class Router<
921
934
  const parseErrors = matchedRoutes.map((route) => {
922
935
  let parsedParamsError
923
936
 
924
- if (route.options.parseParams) {
937
+ const parseParams =
938
+ route.options.params?.parse ?? route.options.parseParams
939
+
940
+ if (parseParams) {
925
941
  try {
926
- const parsedParams = route.options.parseParams(routeParams)
942
+ const parsedParams = parseParams(routeParams)
927
943
  // Add the parsed params to the accumulated params bag
928
944
  Object.assign(routeParams, parsedParams)
929
945
  } catch (err: any) {
@@ -1042,6 +1058,7 @@ export class Router<
1042
1058
 
1043
1059
  match = {
1044
1060
  id: matchId,
1061
+ index,
1045
1062
  routeId: route.id,
1046
1063
  params: routeParams,
1047
1064
  pathname: joinPaths([this.basepath, interpolatedPath]),
@@ -1179,7 +1196,12 @@ export class Router<
1179
1196
 
1180
1197
  if (Object.keys(nextParams).length > 0) {
1181
1198
  matches
1182
- ?.map((d) => this.looseRoutesById[d.routeId]!.options.stringifyParams)
1199
+ ?.map((d) => {
1200
+ const route = this.looseRoutesById[d.routeId]
1201
+ return (
1202
+ route?.options.params?.stringify ?? route!.options.stringifyParams
1203
+ )
1204
+ })
1183
1205
  .filter(Boolean)
1184
1206
  .forEach((fn) => {
1185
1207
  nextParams = { ...nextParams!, ...fn!(nextParams) }
@@ -1958,7 +1980,13 @@ export class Router<
1958
1980
  )
1959
1981
  }
1960
1982
 
1961
- const loaderData = await loaderPromise
1983
+ let loaderData = await loaderPromise
1984
+ if (this.serializeLoaderData) {
1985
+ loaderData = this.serializeLoaderData(loaderData, {
1986
+ router: this,
1987
+ match,
1988
+ })
1989
+ }
1962
1990
  checkLatest()
1963
1991
 
1964
1992
  handleRedirectAndNotFound(match, loaderData)
@@ -2288,38 +2316,29 @@ export class Router<
2288
2316
  return match
2289
2317
  }
2290
2318
 
2291
- // We use a token -> weak map to keep track of deferred promises
2292
- // that are registered on the server and need to be resolved
2293
- registeredDeferredsIds = new Map<string, {}>()
2294
- registeredDeferreds = new WeakMap<{}, DeferredPromiseState<any>>()
2295
-
2296
- getDeferred = (uid: string) => {
2297
- const token = this.registeredDeferredsIds.get(uid)
2298
-
2299
- if (!token) {
2300
- return undefined
2301
- }
2302
-
2303
- return this.registeredDeferreds.get(token)
2304
- }
2305
-
2306
2319
  dehydrate = (): DehydratedRouter => {
2307
2320
  const pickError =
2308
2321
  this.options.errorSerializer?.serialize ?? defaultSerializeError
2309
2322
 
2310
2323
  return {
2311
2324
  state: {
2312
- dehydratedMatches: this.state.matches.map((d) => ({
2313
- ...pick(d, ['id', 'status', 'updatedAt', 'loaderData']),
2314
- // If an error occurs server-side during SSRing,
2315
- // send a small subset of the error to the client
2316
- error: d.error
2317
- ? {
2318
- data: pickError(d.error),
2319
- __isServerError: true,
2320
- }
2321
- : undefined,
2322
- })),
2325
+ dehydratedMatches: this.state.matches.map((d) => {
2326
+ return {
2327
+ ...pick(d, ['id', 'status', 'updatedAt']),
2328
+ // If an error occurs server-side during SSRing,
2329
+ // send a small subset of the error to the client
2330
+ error: d.error
2331
+ ? {
2332
+ data: pickError(d.error),
2333
+ __isServerError: true,
2334
+ }
2335
+ : undefined,
2336
+ // NOTE: We don't send the loader data here, because
2337
+ // there is a potential that it needs to be streamed.
2338
+ // Instead, we render it next to the route match in the HTML
2339
+ // which gives us the potential to stream it via suspense.
2340
+ }
2341
+ }),
2323
2342
  },
2324
2343
  manifest: this.manifest,
2325
2344
  }
@@ -2329,12 +2348,12 @@ export class Router<
2329
2348
  let _ctx = __do_not_use_server_ctx
2330
2349
  // Client hydrates from window
2331
2350
  if (typeof document !== 'undefined') {
2332
- _ctx = window.__TSR_DEHYDRATED__?.data
2351
+ _ctx = window.__TSR__?.dehydrated
2333
2352
  }
2334
2353
 
2335
2354
  invariant(
2336
2355
  _ctx,
2337
- 'Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Please file an issue!',
2356
+ 'Expected to find a dehydrated data on window.__TSR__.dehydrated... but we did not. Please file an issue!',
2338
2357
  )
2339
2358
 
2340
2359
  const ctx = this.options.transformer.parse(_ctx) as HydrationCtx
package/src/useMatch.tsx CHANGED
@@ -3,7 +3,7 @@ import invariant from 'tiny-invariant'
3
3
  import { useRouterState } from './useRouterState'
4
4
  import { type RegisteredRouter } from './router'
5
5
  import { type AnyRoute } from './route'
6
- import { matchContext } from './Matches'
6
+ import { matchContext } from './matchContext'
7
7
  import type { MakeRouteMatch } from './Matches'
8
8
  import type { RouteIds } from './routeInfo'
9
9
  import type { StrictOrFrom } from './utils'