@tanstack/router-core 1.132.0-alpha.1 → 1.132.0-alpha.3

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 (115) hide show
  1. package/dist/cjs/Matches.cjs.map +1 -1
  2. package/dist/cjs/Matches.d.cts +7 -9
  3. package/dist/cjs/index.cjs +8 -2
  4. package/dist/cjs/index.cjs.map +1 -1
  5. package/dist/cjs/index.d.cts +6 -2
  6. package/dist/cjs/load-matches.cjs +636 -0
  7. package/dist/cjs/load-matches.cjs.map +1 -0
  8. package/dist/cjs/load-matches.d.cts +16 -0
  9. package/dist/cjs/qss.cjs +19 -19
  10. package/dist/cjs/qss.cjs.map +1 -1
  11. package/dist/cjs/qss.d.cts +6 -4
  12. package/dist/cjs/redirect.cjs +3 -3
  13. package/dist/cjs/redirect.cjs.map +1 -1
  14. package/dist/cjs/route.cjs.map +1 -1
  15. package/dist/cjs/route.d.cts +0 -4
  16. package/dist/cjs/router.cjs +64 -632
  17. package/dist/cjs/router.cjs.map +1 -1
  18. package/dist/cjs/router.d.cts +14 -26
  19. package/dist/cjs/scroll-restoration.cjs +20 -25
  20. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  21. package/dist/cjs/scroll-restoration.d.cts +0 -9
  22. package/dist/cjs/searchParams.cjs +7 -15
  23. package/dist/cjs/searchParams.cjs.map +1 -1
  24. package/dist/cjs/ssr/constants.cjs +5 -0
  25. package/dist/cjs/ssr/constants.cjs.map +1 -0
  26. package/dist/cjs/ssr/constants.d.cts +1 -0
  27. package/dist/cjs/ssr/{seroval-plugins.cjs → serializer/ShallowErrorPlugin.cjs} +2 -2
  28. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -0
  29. package/dist/cjs/ssr/{seroval-plugins.d.cts → serializer/ShallowErrorPlugin.d.cts} +1 -2
  30. package/dist/cjs/ssr/serializer/seroval-plugins.cjs +11 -0
  31. package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -0
  32. package/dist/cjs/ssr/serializer/seroval-plugins.d.cts +2 -0
  33. package/dist/cjs/ssr/serializer/transformer.cjs +50 -0
  34. package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -0
  35. package/dist/cjs/ssr/serializer/transformer.d.cts +18 -0
  36. package/dist/cjs/ssr/ssr-client.cjs +53 -40
  37. package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
  38. package/dist/cjs/ssr/ssr-client.d.cts +5 -1
  39. package/dist/cjs/ssr/ssr-server.cjs +12 -10
  40. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  41. package/dist/cjs/ssr/ssr-server.d.cts +0 -1
  42. package/dist/cjs/ssr/tsrScript.cjs +1 -1
  43. package/dist/cjs/ssr/tsrScript.cjs.map +1 -1
  44. package/dist/cjs/typePrimitives.d.cts +6 -6
  45. package/dist/cjs/utils.cjs +14 -7
  46. package/dist/cjs/utils.cjs.map +1 -1
  47. package/dist/cjs/utils.d.cts +2 -1
  48. package/dist/esm/Matches.d.ts +7 -9
  49. package/dist/esm/Matches.js.map +1 -1
  50. package/dist/esm/index.d.ts +6 -2
  51. package/dist/esm/index.js +9 -3
  52. package/dist/esm/index.js.map +1 -1
  53. package/dist/esm/load-matches.d.ts +16 -0
  54. package/dist/esm/load-matches.js +636 -0
  55. package/dist/esm/load-matches.js.map +1 -0
  56. package/dist/esm/qss.d.ts +6 -4
  57. package/dist/esm/qss.js +19 -19
  58. package/dist/esm/qss.js.map +1 -1
  59. package/dist/esm/redirect.js +3 -3
  60. package/dist/esm/redirect.js.map +1 -1
  61. package/dist/esm/route.d.ts +0 -4
  62. package/dist/esm/route.js.map +1 -1
  63. package/dist/esm/router.d.ts +14 -26
  64. package/dist/esm/router.js +64 -632
  65. package/dist/esm/router.js.map +1 -1
  66. package/dist/esm/scroll-restoration.d.ts +0 -9
  67. package/dist/esm/scroll-restoration.js +20 -25
  68. package/dist/esm/scroll-restoration.js.map +1 -1
  69. package/dist/esm/searchParams.js +7 -15
  70. package/dist/esm/searchParams.js.map +1 -1
  71. package/dist/esm/ssr/constants.d.ts +1 -0
  72. package/dist/esm/ssr/constants.js +5 -0
  73. package/dist/esm/ssr/constants.js.map +1 -0
  74. package/dist/esm/ssr/{seroval-plugins.d.ts → serializer/ShallowErrorPlugin.d.ts} +1 -2
  75. package/dist/esm/ssr/{seroval-plugins.js → serializer/ShallowErrorPlugin.js} +2 -2
  76. package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -0
  77. package/dist/esm/ssr/serializer/seroval-plugins.d.ts +2 -0
  78. package/dist/esm/ssr/serializer/seroval-plugins.js +11 -0
  79. package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -0
  80. package/dist/esm/ssr/serializer/transformer.d.ts +18 -0
  81. package/dist/esm/ssr/serializer/transformer.js +50 -0
  82. package/dist/esm/ssr/serializer/transformer.js.map +1 -0
  83. package/dist/esm/ssr/ssr-client.d.ts +5 -1
  84. package/dist/esm/ssr/ssr-client.js +53 -40
  85. package/dist/esm/ssr/ssr-client.js.map +1 -1
  86. package/dist/esm/ssr/ssr-server.d.ts +0 -1
  87. package/dist/esm/ssr/ssr-server.js +12 -10
  88. package/dist/esm/ssr/ssr-server.js.map +1 -1
  89. package/dist/esm/ssr/tsrScript.js +1 -1
  90. package/dist/esm/ssr/tsrScript.js.map +1 -1
  91. package/dist/esm/typePrimitives.d.ts +6 -6
  92. package/dist/esm/utils.d.ts +2 -1
  93. package/dist/esm/utils.js +14 -7
  94. package/dist/esm/utils.js.map +1 -1
  95. package/package.json +1 -1
  96. package/src/Matches.ts +16 -8
  97. package/src/index.ts +12 -2
  98. package/src/load-matches.ts +955 -0
  99. package/src/qss.ts +27 -24
  100. package/src/redirect.ts +3 -3
  101. package/src/route.ts +10 -2
  102. package/src/router.ts +99 -893
  103. package/src/scroll-restoration.ts +25 -32
  104. package/src/searchParams.ts +8 -19
  105. package/src/ssr/constants.ts +1 -0
  106. package/src/ssr/{seroval-plugins.ts → serializer/ShallowErrorPlugin.ts} +2 -2
  107. package/src/ssr/serializer/seroval-plugins.ts +9 -0
  108. package/src/ssr/serializer/transformer.ts +78 -0
  109. package/src/ssr/ssr-client.ts +72 -44
  110. package/src/ssr/ssr-server.ts +18 -10
  111. package/src/ssr/tsrScript.ts +5 -1
  112. package/src/typePrimitives.ts +6 -6
  113. package/src/utils.ts +21 -10
  114. package/dist/cjs/ssr/seroval-plugins.cjs.map +0 -1
  115. package/dist/esm/ssr/seroval-plugins.js.map +0 -1
package/src/router.ts CHANGED
@@ -8,9 +8,9 @@ import invariant from 'tiny-invariant'
8
8
  import {
9
9
  createControlledPromise,
10
10
  deepEqual,
11
+ findLast,
11
12
  functionalUpdate,
12
13
  last,
13
- pick,
14
14
  replaceEqualDeep,
15
15
  } from './utils'
16
16
  import {
@@ -34,6 +34,7 @@ import { defaultParseSearch, defaultStringifySearch } from './searchParams'
34
34
  import { rootRouteId } from './root'
35
35
  import { isRedirect, redirect } from './redirect'
36
36
  import { createLRUCache } from './lru-cache'
37
+ import { loadMatches, loadRouteChunk, routeNeedsPreload } from './load-matches'
37
38
  import type { ParsePathnameCache, Segment } from './path'
38
39
  import type { SearchParser, SearchSerializer } from './searchParams'
39
40
  import type { AnyRedirect, ResolvedRedirect } from './redirect'
@@ -56,13 +57,10 @@ import type {
56
57
  AnyContext,
57
58
  AnyRoute,
58
59
  AnyRouteWithContext,
59
- BeforeLoadContextOptions,
60
- LoaderFnContext,
61
60
  MakeRemountDepsOptionsUnion,
62
61
  RouteContextOptions,
63
62
  RouteMask,
64
63
  SearchMiddleware,
65
- SsrContextOptions,
66
64
  } from './route'
67
65
  import type {
68
66
  FullSearchSchema,
@@ -119,6 +117,7 @@ export interface RouterOptions<
119
117
  TDefaultStructuralSharingOption extends boolean = false,
120
118
  TRouterHistory extends RouterHistory = RouterHistory,
121
119
  TDehydrated extends Record<string, any> = Record<string, any>,
120
+ TTransformerConfig = any,
122
121
  > extends RouterOptionsExtensions {
123
122
  /**
124
123
  * The history object that will be used to manage the browser history.
@@ -423,6 +422,8 @@ export interface RouterOptions<
423
422
  * @default false
424
423
  */
425
424
  disableGlobalCatchBoundary?: boolean
425
+
426
+ serializationAdapters?: TTransformerConfig
426
427
  }
427
428
 
428
429
  export interface RouterState<
@@ -531,13 +532,15 @@ export type RouterConstructorOptions<
531
532
  TDefaultStructuralSharingOption extends boolean,
532
533
  TRouterHistory extends RouterHistory,
533
534
  TDehydrated extends Record<string, any>,
535
+ TTransformerConfig,
534
536
  > = Omit<
535
537
  RouterOptions<
536
538
  TRouteTree,
537
539
  TTrailingSlashOption,
538
540
  TDefaultStructuralSharingOption,
539
541
  TRouterHistory,
540
- TDehydrated
542
+ TDehydrated,
543
+ TTransformerConfig
541
544
  >,
542
545
  'context'
543
546
  > &
@@ -597,13 +600,15 @@ export type UpdateFn<
597
600
  TDefaultStructuralSharingOption extends boolean,
598
601
  TRouterHistory extends RouterHistory,
599
602
  TDehydrated extends Record<string, any>,
603
+ TTransformerConfig extends any,
600
604
  > = (
601
605
  newOptions: RouterConstructorOptions<
602
606
  TRouteTree,
603
607
  TTrailingSlashOption,
604
608
  TDefaultStructuralSharingOption,
605
609
  TRouterHistory,
606
- TDehydrated
610
+ TDehydrated,
611
+ TTransformerConfig
607
612
  >,
608
613
  ) => void
609
614
 
@@ -688,10 +693,11 @@ export type AnyRouterWithContext<TContext> = RouterCore<
688
693
  any,
689
694
  any,
690
695
  any,
696
+ any,
691
697
  any
692
698
  >
693
699
 
694
- export type AnyRouter = RouterCore<any, any, any, any, any>
700
+ export type AnyRouter = RouterCore<any, any, any, any, any, any>
695
701
 
696
702
  export interface ViewTransitionOptions {
697
703
  types:
@@ -705,6 +711,7 @@ export interface ViewTransitionOptions {
705
711
  }) => Array<string>)
706
712
  }
707
713
 
714
+ // TODO where is this used? can we remove this?
708
715
  export function defaultSerializeError(err: unknown) {
709
716
  if (err instanceof Error) {
710
717
  const obj = {
@@ -744,6 +751,7 @@ export type CreateRouterFn = <
744
751
  TDefaultStructuralSharingOption extends boolean = false,
745
752
  TRouterHistory extends RouterHistory = RouterHistory,
746
753
  TDehydrated extends Record<string, any> = Record<string, any>,
754
+ TTransformerConfig = any,
747
755
  >(
748
756
  options: undefined extends number
749
757
  ? 'strictNullChecks must be enabled in tsconfig.json'
@@ -752,14 +760,16 @@ export type CreateRouterFn = <
752
760
  TTrailingSlashOption,
753
761
  TDefaultStructuralSharingOption,
754
762
  TRouterHistory,
755
- TDehydrated
763
+ TDehydrated,
764
+ TTransformerConfig
756
765
  >,
757
766
  ) => RouterCore<
758
767
  TRouteTree,
759
768
  TTrailingSlashOption,
760
769
  TDefaultStructuralSharingOption,
761
770
  TRouterHistory,
762
- TDehydrated
771
+ TDehydrated,
772
+ TTransformerConfig
763
773
  >
764
774
 
765
775
  export class RouterCore<
@@ -768,6 +778,7 @@ export class RouterCore<
768
778
  in out TDefaultStructuralSharingOption extends boolean,
769
779
  in out TRouterHistory extends RouterHistory = RouterHistory,
770
780
  in out TDehydrated extends Record<string, any> = Record<string, any>,
781
+ in out TTransformerConfig = any,
771
782
  > {
772
783
  // Option-independent properties
773
784
  tempLocationKey: string | undefined = `${Math.round(
@@ -789,7 +800,8 @@ export class RouterCore<
789
800
  TTrailingSlashOption,
790
801
  TDefaultStructuralSharingOption,
791
802
  TRouterHistory,
792
- TDehydrated
803
+ TDehydrated,
804
+ TTransformerConfig
793
805
  >,
794
806
  'stringifySearch' | 'parseSearch' | 'context'
795
807
  >
@@ -812,7 +824,8 @@ export class RouterCore<
812
824
  TTrailingSlashOption,
813
825
  TDefaultStructuralSharingOption,
814
826
  TRouterHistory,
815
- TDehydrated
827
+ TDehydrated,
828
+ TTransformerConfig
816
829
  >,
817
830
  ) {
818
831
  this.update({
@@ -850,7 +863,8 @@ export class RouterCore<
850
863
  TTrailingSlashOption,
851
864
  TDefaultStructuralSharingOption,
852
865
  TRouterHistory,
853
- TDehydrated
866
+ TDehydrated,
867
+ TTransformerConfig
854
868
  > = (newOptions) => {
855
869
  if (newOptions.notFoundRoute) {
856
870
  console.warn(
@@ -1139,8 +1153,8 @@ export class RouterCore<
1139
1153
  const parentMatchId = parentMatch?.id
1140
1154
 
1141
1155
  const parentContext = !parentMatchId
1142
- ? ((this.options.context as any) ?? {})
1143
- : (parentMatch.context ?? this.options.context ?? {})
1156
+ ? ((this.options.context as any) ?? undefined)
1157
+ : (parentMatch.context ?? this.options.context ?? undefined)
1144
1158
 
1145
1159
  return parentContext
1146
1160
  }
@@ -1162,12 +1176,12 @@ export class RouterCore<
1162
1176
  ] = (() => {
1163
1177
  // Validate the search params and stabilize them
1164
1178
  const parentSearch = parentMatch?.search ?? next.search
1165
- const parentStrictSearch = parentMatch?._strictSearch ?? {}
1179
+ const parentStrictSearch = parentMatch?._strictSearch ?? undefined
1166
1180
 
1167
1181
  try {
1168
1182
  const strictSearch =
1169
1183
  validateSearch(route.options.validateSearch, { ...parentSearch }) ??
1170
- {}
1184
+ undefined
1171
1185
 
1172
1186
  return [
1173
1187
  {
@@ -1277,7 +1291,10 @@ export class RouterCore<
1277
1291
  isFetching: false,
1278
1292
  error: undefined,
1279
1293
  paramsError: parseErrors[index],
1280
- __routeContext: {},
1294
+ __routeContext: undefined,
1295
+ _nonReactive: {
1296
+ loadPromise: createControlledPromise(),
1297
+ },
1281
1298
  __beforeLoadContext: undefined,
1282
1299
  context: {},
1283
1300
  abortController: new AbortController(),
@@ -1293,7 +1310,6 @@ export class RouterCore<
1293
1310
  headScripts: undefined,
1294
1311
  meta: undefined,
1295
1312
  staticData: route.options.staticData || {},
1296
- loadPromise: createControlledPromise(),
1297
1313
  fullPath: route.fullPath,
1298
1314
  }
1299
1315
  }
@@ -1328,22 +1344,25 @@ export class RouterCore<
1328
1344
  const parentContext = getParentContext(parentMatch)
1329
1345
 
1330
1346
  // Update the match's context
1331
- const contextFnContext: RouteContextOptions<any, any, any, any> = {
1332
- deps: match.loaderDeps,
1333
- params: match.params,
1334
- context: parentContext,
1335
- location: next,
1336
- navigate: (opts: any) =>
1337
- this.navigate({ ...opts, _fromLocation: next }),
1338
- buildLocation: this.buildLocation,
1339
- cause: match.cause,
1340
- abortController: match.abortController,
1341
- preload: !!match.preload,
1342
- matches,
1343
- }
1344
1347
 
1345
- // Get the route context
1346
- match.__routeContext = route.options.context?.(contextFnContext) ?? {}
1348
+ if (route.options.context) {
1349
+ const contextFnContext: RouteContextOptions<any, any, any, any> = {
1350
+ deps: match.loaderDeps,
1351
+ params: match.params,
1352
+ context: parentContext ?? {},
1353
+ location: next,
1354
+ navigate: (opts: any) =>
1355
+ this.navigate({ ...opts, _fromLocation: next }),
1356
+ buildLocation: this.buildLocation,
1357
+ cause: match.cause,
1358
+ abortController: match.abortController,
1359
+ preload: !!match.preload,
1360
+ matches,
1361
+ }
1362
+ // Get the route context
1363
+ match.__routeContext =
1364
+ route.options.context(contextFnContext) ?? undefined
1365
+ }
1347
1366
 
1348
1367
  match.context = {
1349
1368
  ...parentContext,
@@ -1381,13 +1400,8 @@ export class RouterCore<
1381
1400
  if (!match) return
1382
1401
 
1383
1402
  match.abortController.abort()
1384
- this.updateMatch(id, (prev) => {
1385
- clearTimeout(prev.pendingTimeout)
1386
- return {
1387
- ...prev,
1388
- pendingTimeout: undefined,
1389
- }
1390
- })
1403
+ clearTimeout(match._nonReactive.pendingTimeout)
1404
+ match._nonReactive.pendingTimeout = undefined
1391
1405
  }
1392
1406
 
1393
1407
  cancelMatches = () => {
@@ -1413,7 +1427,7 @@ export class RouterCore<
1413
1427
 
1414
1428
  // First let's find the starting pathname
1415
1429
  // By default, start with the current location
1416
- let fromPath = lastMatch.fullPath
1430
+ let fromPath = this.resolvePathWithBase(lastMatch.fullPath, '.')
1417
1431
  const toPath = dest.to
1418
1432
  ? this.resolvePathWithBase(fromPath, `${dest.to}`)
1419
1433
  : this.resolvePathWithBase(fromPath, '.')
@@ -1436,13 +1450,11 @@ export class RouterCore<
1436
1450
  undefined,
1437
1451
  ).matchedRoutes
1438
1452
 
1439
- const matchedFrom = [...allCurrentLocationMatches]
1440
- .reverse()
1441
- .find((d) => {
1442
- return comparePaths(d.fullPath, fromPath)
1443
- })
1453
+ const matchedFrom = findLast(allCurrentLocationMatches, (d) => {
1454
+ return comparePaths(d.fullPath, fromPath)
1455
+ })
1444
1456
 
1445
- const matchedCurrent = [...allFromMatches].reverse().find((d) => {
1457
+ const matchedCurrent = findLast(allFromMatches, (d) => {
1446
1458
  return comparePaths(d.fullPath, currentLocation.pathname)
1447
1459
  })
1448
1460
 
@@ -1454,6 +1466,8 @@ export class RouterCore<
1454
1466
  }
1455
1467
  }
1456
1468
 
1469
+ fromPath = this.resolvePathWithBase(fromPath, '.')
1470
+
1457
1471
  // From search should always use the current location
1458
1472
  const fromSearch = lastMatch.search
1459
1473
  // Same with params. It can't hurt to provide as many as possible
@@ -1465,50 +1479,43 @@ export class RouterCore<
1465
1479
  : this.resolvePathWithBase(fromPath, '.')
1466
1480
 
1467
1481
  // Resolve the next params
1468
- let nextParams =
1482
+ const nextParams =
1469
1483
  dest.params === false || dest.params === null
1470
1484
  ? {}
1471
1485
  : (dest.params ?? true) === true
1472
1486
  ? fromParams
1473
- : {
1474
- ...fromParams,
1475
- ...functionalUpdate(dest.params as any, fromParams),
1476
- }
1487
+ : Object.assign(
1488
+ fromParams,
1489
+ functionalUpdate(dest.params as any, fromParams),
1490
+ )
1477
1491
 
1478
1492
  // Interpolate the path first to get the actual resolved path, then match against that
1479
1493
  const interpolatedNextTo = interpolatePath({
1480
1494
  path: nextTo,
1481
- params: nextParams ?? {},
1495
+ params: nextParams,
1482
1496
  parseCache: this.parsePathnameCache,
1483
1497
  }).interpolatedPath
1484
1498
 
1485
- const destRoutes = this.matchRoutes(
1486
- interpolatedNextTo,
1487
- {},
1488
- {
1489
- _buildLocation: true,
1490
- },
1491
- ).map((d) => this.looseRoutesById[d.routeId]!)
1499
+ const destRoutes = this.matchRoutes(interpolatedNextTo, undefined, {
1500
+ _buildLocation: true,
1501
+ }).map((d) => this.looseRoutesById[d.routeId]!)
1492
1502
 
1493
1503
  // If there are any params, we need to stringify them
1494
1504
  if (Object.keys(nextParams).length > 0) {
1495
- destRoutes
1496
- .map((route) => {
1497
- return (
1498
- route.options.params?.stringify ?? route.options.stringifyParams
1499
- )
1500
- })
1501
- .filter(Boolean)
1502
- .forEach((fn) => {
1503
- nextParams = { ...nextParams!, ...fn!(nextParams) }
1504
- })
1505
+ for (const route of destRoutes) {
1506
+ const fn =
1507
+ route.options.params?.stringify ?? route.options.stringifyParams
1508
+ if (fn) {
1509
+ Object.assign(nextParams, fn(nextParams))
1510
+ }
1511
+ }
1505
1512
  }
1506
1513
 
1507
1514
  const nextPathname = interpolatePath({
1508
1515
  // Use the original template path for interpolation
1509
1516
  // This preserves the original parameter syntax including optional parameters
1510
1517
  path: nextTo,
1511
- params: nextParams ?? {},
1518
+ params: nextParams,
1512
1519
  leaveWildcards: false,
1513
1520
  leaveParams: opts.leaveParams,
1514
1521
  decodeCharMap: this.pathParamsDecodeCharMap,
@@ -1518,20 +1525,20 @@ export class RouterCore<
1518
1525
  // Resolve the next search
1519
1526
  let nextSearch = fromSearch
1520
1527
  if (opts._includeValidateSearch && this.options.search?.strict) {
1521
- let validatedSearch = {}
1528
+ const validatedSearch = {}
1522
1529
  destRoutes.forEach((route) => {
1523
- try {
1524
- if (route.options.validateSearch) {
1525
- validatedSearch = {
1526
- ...validatedSearch,
1527
- ...(validateSearch(route.options.validateSearch, {
1530
+ if (route.options.validateSearch) {
1531
+ try {
1532
+ Object.assign(
1533
+ validatedSearch,
1534
+ validateSearch(route.options.validateSearch, {
1528
1535
  ...validatedSearch,
1529
1536
  ...nextSearch,
1530
- }) ?? {}),
1531
- }
1537
+ }),
1538
+ )
1539
+ } catch {
1540
+ // ignore errors here because they are already handled in matchRoutes
1532
1541
  }
1533
- } catch {
1534
- // ignore errors here because they are already handled in matchRoutes
1535
1542
  }
1536
1543
  })
1537
1544
  nextSearch = validatedSearch
@@ -1618,7 +1625,7 @@ export class RouterCore<
1618
1625
  if (foundMask) {
1619
1626
  const { from: _from, ...maskProps } = foundMask
1620
1627
  maskedDest = {
1621
- ...pick(opts, ['from']),
1628
+ from: opts.from,
1622
1629
  ...maskProps,
1623
1630
  params,
1624
1631
  }
@@ -1636,7 +1643,7 @@ export class RouterCore<
1636
1643
 
1637
1644
  if (opts.mask) {
1638
1645
  return buildWithMatches(opts, {
1639
- ...pick(opts, ['from']),
1646
+ from: opts.from,
1640
1647
  ...opts.mask,
1641
1648
  })
1642
1649
  }
@@ -1884,10 +1891,12 @@ export class RouterCore<
1884
1891
  }),
1885
1892
  })
1886
1893
 
1887
- await this.loadMatches({
1894
+ await loadMatches({
1895
+ router: this,
1888
1896
  sync: opts?.sync,
1889
1897
  matches: this.state.pendingMatches as Array<AnyRouteMatch>,
1890
1898
  location: next,
1899
+ updateMatch: this.updateMatch,
1891
1900
  // eslint-disable-next-line @typescript-eslint/require-await
1892
1901
  onReady: async () => {
1893
1902
  // eslint-disable-next-line @typescript-eslint/require-await
@@ -2077,704 +2086,6 @@ export class RouterCore<
2077
2086
  )
2078
2087
  }
2079
2088
 
2080
- loadMatches = async ({
2081
- location,
2082
- matches,
2083
- preload: allPreload,
2084
- onReady,
2085
- updateMatch = this.updateMatch,
2086
- sync,
2087
- }: {
2088
- location: ParsedLocation
2089
- matches: Array<AnyRouteMatch>
2090
- preload?: boolean
2091
- onReady?: () => Promise<void>
2092
- updateMatch?: (
2093
- id: string,
2094
- updater: (match: AnyRouteMatch) => AnyRouteMatch,
2095
- ) => void
2096
- getMatch?: (matchId: string) => AnyRouteMatch | undefined
2097
- sync?: boolean
2098
- }): Promise<Array<MakeRouteMatch>> => {
2099
- let firstBadMatchIndex: number | undefined
2100
- let rendered = false
2101
-
2102
- const triggerOnReady = async () => {
2103
- if (!rendered) {
2104
- rendered = true
2105
- await onReady?.()
2106
- }
2107
- }
2108
-
2109
- const resolvePreload = (matchId: string) => {
2110
- return !!(allPreload && !this.state.matches.some((d) => d.id === matchId))
2111
- }
2112
-
2113
- // make sure the pending component is immediately rendered when hydrating a match that is not SSRed
2114
- // the pending component was already rendered on the server and we want to keep it shown on the client until minPendingMs is reached
2115
- if (!this.isServer && this.state.matches.some((d) => d._forcePending)) {
2116
- triggerOnReady()
2117
- }
2118
-
2119
- const handleRedirectAndNotFound = (match: AnyRouteMatch, err: any) => {
2120
- if (isRedirect(err) || isNotFound(err)) {
2121
- if (isRedirect(err)) {
2122
- if (err.redirectHandled) {
2123
- if (!err.options.reloadDocument) {
2124
- throw err
2125
- }
2126
- }
2127
- }
2128
-
2129
- match.beforeLoadPromise?.resolve()
2130
- match.loaderPromise?.resolve()
2131
-
2132
- updateMatch(match.id, (prev) => ({
2133
- ...prev,
2134
- status: isRedirect(err)
2135
- ? 'redirected'
2136
- : isNotFound(err)
2137
- ? 'notFound'
2138
- : 'error',
2139
- isFetching: false,
2140
- error: err,
2141
- beforeLoadPromise: undefined,
2142
- loaderPromise: undefined,
2143
- }))
2144
-
2145
- if (!(err as any).routeId) {
2146
- ;(err as any).routeId = match.routeId
2147
- }
2148
-
2149
- match.loadPromise?.resolve()
2150
-
2151
- if (isRedirect(err)) {
2152
- rendered = true
2153
- err.options._fromLocation = location
2154
- err.redirectHandled = true
2155
- err = this.resolveRedirect(err)
2156
- throw err
2157
- } else if (isNotFound(err)) {
2158
- this._handleNotFound(matches, err, {
2159
- updateMatch,
2160
- })
2161
- throw err
2162
- }
2163
- }
2164
- }
2165
-
2166
- const shouldSkipLoader = (matchId: string) => {
2167
- const match = this.getMatch(matchId)!
2168
- // upon hydration, we skip the loader if the match has been dehydrated on the server
2169
- if (!this.isServer && match._dehydrated) {
2170
- return true
2171
- }
2172
-
2173
- if (this.isServer) {
2174
- if (match.ssr === false) {
2175
- return true
2176
- }
2177
- }
2178
- return false
2179
- }
2180
-
2181
- try {
2182
- await new Promise<void>((resolveAll, rejectAll) => {
2183
- ;(async () => {
2184
- try {
2185
- const handleSerialError = (
2186
- index: number,
2187
- err: any,
2188
- routerCode: string,
2189
- ) => {
2190
- const { id: matchId, routeId } = matches[index]!
2191
- const route = this.looseRoutesById[routeId]!
2192
-
2193
- // Much like suspense, we use a promise here to know if
2194
- // we've been outdated by a new loadMatches call and
2195
- // should abort the current async operation
2196
- if (err instanceof Promise) {
2197
- throw err
2198
- }
2199
-
2200
- err.routerCode = routerCode
2201
- firstBadMatchIndex = firstBadMatchIndex ?? index
2202
- handleRedirectAndNotFound(this.getMatch(matchId)!, err)
2203
-
2204
- try {
2205
- route.options.onError?.(err)
2206
- } catch (errorHandlerErr) {
2207
- err = errorHandlerErr
2208
- handleRedirectAndNotFound(this.getMatch(matchId)!, err)
2209
- }
2210
-
2211
- updateMatch(matchId, (prev) => {
2212
- prev.beforeLoadPromise?.resolve()
2213
- prev.loadPromise?.resolve()
2214
-
2215
- return {
2216
- ...prev,
2217
- error: err,
2218
- status: 'error',
2219
- isFetching: false,
2220
- updatedAt: Date.now(),
2221
- abortController: new AbortController(),
2222
- beforeLoadPromise: undefined,
2223
- }
2224
- })
2225
- }
2226
-
2227
- for (const [index, { id: matchId, routeId }] of matches.entries()) {
2228
- const existingMatch = this.getMatch(matchId)!
2229
- const parentMatchId = matches[index - 1]?.id
2230
- const parentMatch = parentMatchId
2231
- ? this.getMatch(parentMatchId)!
2232
- : undefined
2233
-
2234
- const route = this.looseRoutesById[routeId]!
2235
-
2236
- const pendingMs =
2237
- route.options.pendingMs ?? this.options.defaultPendingMs
2238
-
2239
- // on the server, determine whether SSR the current match or not
2240
- if (this.isServer) {
2241
- let ssr: boolean | 'data-only'
2242
- // in SPA mode, only SSR the root route
2243
- if (this.isShell()) {
2244
- ssr = matchId === rootRouteId
2245
- } else {
2246
- const defaultSsr = this.options.defaultSsr ?? true
2247
- if (parentMatch?.ssr === false) {
2248
- ssr = false
2249
- } else {
2250
- let tempSsr: boolean | 'data-only'
2251
- if (route.options.ssr === undefined) {
2252
- tempSsr = defaultSsr
2253
- } else if (typeof route.options.ssr === 'function') {
2254
- const { search, params } = this.getMatch(matchId)!
2255
-
2256
- function makeMaybe(value: any, error: any) {
2257
- if (error) {
2258
- return { status: 'error' as const, error }
2259
- }
2260
- return { status: 'success' as const, value }
2261
- }
2262
-
2263
- const ssrFnContext: SsrContextOptions<any, any, any> = {
2264
- search: makeMaybe(search, existingMatch.searchError),
2265
- params: makeMaybe(params, existingMatch.paramsError),
2266
- location,
2267
- matches: matches.map((match) => ({
2268
- index: match.index,
2269
- pathname: match.pathname,
2270
- fullPath: match.fullPath,
2271
- staticData: match.staticData,
2272
- id: match.id,
2273
- routeId: match.routeId,
2274
- search: makeMaybe(match.search, match.searchError),
2275
- params: makeMaybe(match.params, match.paramsError),
2276
- ssr: match.ssr,
2277
- })),
2278
- }
2279
- tempSsr =
2280
- (await route.options.ssr(ssrFnContext)) ?? defaultSsr
2281
- } else {
2282
- tempSsr = route.options.ssr
2283
- }
2284
-
2285
- if (tempSsr === true && parentMatch?.ssr === 'data-only') {
2286
- ssr = 'data-only'
2287
- } else {
2288
- ssr = tempSsr
2289
- }
2290
- }
2291
- }
2292
- updateMatch(matchId, (prev) => ({
2293
- ...prev,
2294
- ssr,
2295
- }))
2296
- }
2297
-
2298
- if (shouldSkipLoader(matchId)) {
2299
- continue
2300
- }
2301
-
2302
- const shouldPending = !!(
2303
- onReady &&
2304
- !this.isServer &&
2305
- !resolvePreload(matchId) &&
2306
- (route.options.loader ||
2307
- route.options.beforeLoad ||
2308
- routeNeedsPreload(route)) &&
2309
- typeof pendingMs === 'number' &&
2310
- pendingMs !== Infinity &&
2311
- (route.options.pendingComponent ??
2312
- (this.options as any)?.defaultPendingComponent)
2313
- )
2314
-
2315
- let executeBeforeLoad = true
2316
- const setupPendingTimeout = () => {
2317
- if (
2318
- shouldPending &&
2319
- this.getMatch(matchId)!.pendingTimeout === undefined
2320
- ) {
2321
- const pendingTimeout = setTimeout(() => {
2322
- try {
2323
- // Update the match and prematurely resolve the loadMatches promise so that
2324
- // the pending component can start rendering
2325
- triggerOnReady()
2326
- } catch {}
2327
- }, pendingMs)
2328
- updateMatch(matchId, (prev) => ({
2329
- ...prev,
2330
- pendingTimeout,
2331
- }))
2332
- }
2333
- }
2334
- if (
2335
- // If we are in the middle of a load, either of these will be present
2336
- // (not to be confused with `loadPromise`, which is always defined)
2337
- existingMatch.beforeLoadPromise ||
2338
- existingMatch.loaderPromise
2339
- ) {
2340
- setupPendingTimeout()
2341
-
2342
- // Wait for the beforeLoad to resolve before we continue
2343
- await existingMatch.beforeLoadPromise
2344
- const match = this.getMatch(matchId)!
2345
- if (match.status === 'error') {
2346
- executeBeforeLoad = true
2347
- } else if (
2348
- match.preload &&
2349
- (match.status === 'redirected' || match.status === 'notFound')
2350
- ) {
2351
- handleRedirectAndNotFound(match, match.error)
2352
- }
2353
- }
2354
- if (executeBeforeLoad) {
2355
- // If we are not in the middle of a load OR the previous load failed, start it
2356
- try {
2357
- updateMatch(matchId, (prev) => {
2358
- // explicitly capture the previous loadPromise
2359
- const prevLoadPromise = prev.loadPromise
2360
- return {
2361
- ...prev,
2362
- loadPromise: createControlledPromise<void>(() => {
2363
- prevLoadPromise?.resolve()
2364
- }),
2365
- beforeLoadPromise: createControlledPromise<void>(),
2366
- }
2367
- })
2368
-
2369
- const { paramsError, searchError } = this.getMatch(matchId)!
2370
-
2371
- if (paramsError) {
2372
- handleSerialError(index, paramsError, 'PARSE_PARAMS')
2373
- }
2374
-
2375
- if (searchError) {
2376
- handleSerialError(index, searchError, 'VALIDATE_SEARCH')
2377
- }
2378
-
2379
- setupPendingTimeout()
2380
-
2381
- const abortController = new AbortController()
2382
-
2383
- const parentMatchContext =
2384
- parentMatch?.context ?? this.options.context ?? {}
2385
-
2386
- updateMatch(matchId, (prev) => ({
2387
- ...prev,
2388
- isFetching: 'beforeLoad',
2389
- fetchCount: prev.fetchCount + 1,
2390
- abortController,
2391
- context: {
2392
- ...parentMatchContext,
2393
- ...prev.__routeContext,
2394
- },
2395
- }))
2396
-
2397
- const { search, params, context, cause } =
2398
- this.getMatch(matchId)!
2399
-
2400
- const preload = resolvePreload(matchId)
2401
-
2402
- const beforeLoadFnContext: BeforeLoadContextOptions<
2403
- any,
2404
- any,
2405
- any,
2406
- any,
2407
- any
2408
- > = {
2409
- search,
2410
- abortController,
2411
- params,
2412
- preload,
2413
- context,
2414
- location,
2415
- navigate: (opts: any) =>
2416
- this.navigate({ ...opts, _fromLocation: location }),
2417
- buildLocation: this.buildLocation,
2418
- cause: preload ? 'preload' : cause,
2419
- matches,
2420
- }
2421
-
2422
- const beforeLoadContext =
2423
- await route.options.beforeLoad?.(beforeLoadFnContext)
2424
-
2425
- if (
2426
- isRedirect(beforeLoadContext) ||
2427
- isNotFound(beforeLoadContext)
2428
- ) {
2429
- handleSerialError(index, beforeLoadContext, 'BEFORE_LOAD')
2430
- }
2431
-
2432
- updateMatch(matchId, (prev) => {
2433
- return {
2434
- ...prev,
2435
- __beforeLoadContext: beforeLoadContext,
2436
- context: {
2437
- ...parentMatchContext,
2438
- ...prev.__routeContext,
2439
- ...beforeLoadContext,
2440
- },
2441
- abortController,
2442
- }
2443
- })
2444
- } catch (err) {
2445
- handleSerialError(index, err, 'BEFORE_LOAD')
2446
- }
2447
-
2448
- updateMatch(matchId, (prev) => {
2449
- prev.beforeLoadPromise?.resolve()
2450
-
2451
- return {
2452
- ...prev,
2453
- beforeLoadPromise: undefined,
2454
- isFetching: false,
2455
- }
2456
- })
2457
- }
2458
- }
2459
-
2460
- const validResolvedMatches = matches.slice(0, firstBadMatchIndex)
2461
- const matchPromises: Array<Promise<AnyRouteMatch>> = []
2462
-
2463
- validResolvedMatches.forEach(({ id: matchId, routeId }, index) => {
2464
- matchPromises.push(
2465
- (async () => {
2466
- let loaderShouldRunAsync = false
2467
- let loaderIsRunningAsync = false
2468
- const route = this.looseRoutesById[routeId]!
2469
-
2470
- const executeHead = async () => {
2471
- const match = this.getMatch(matchId)
2472
- // in case of a redirecting match during preload, the match does not exist
2473
- if (!match) {
2474
- return
2475
- }
2476
- const assetContext = {
2477
- matches,
2478
- match,
2479
- params: match.params,
2480
- loaderData: match.loaderData,
2481
- }
2482
- const headFnContent =
2483
- await route.options.head?.(assetContext)
2484
- const meta = headFnContent?.meta
2485
- const links = headFnContent?.links
2486
- const headScripts = headFnContent?.scripts
2487
- const styles = headFnContent?.styles
2488
-
2489
- const scripts = await route.options.scripts?.(assetContext)
2490
- const headers = await route.options.headers?.(assetContext)
2491
- return {
2492
- meta,
2493
- links,
2494
- headScripts,
2495
- headers,
2496
- scripts,
2497
- styles,
2498
- }
2499
- }
2500
-
2501
- const potentialPendingMinPromise = async () => {
2502
- const latestMatch = this.getMatch(matchId)!
2503
- if (latestMatch.minPendingPromise) {
2504
- await latestMatch.minPendingPromise
2505
- }
2506
- }
2507
-
2508
- const prevMatch = this.getMatch(matchId)!
2509
- if (shouldSkipLoader(matchId)) {
2510
- if (this.isServer) {
2511
- const head = await executeHead()
2512
- updateMatch(matchId, (prev) => ({
2513
- ...prev,
2514
- ...head,
2515
- }))
2516
- return this.getMatch(matchId)!
2517
- }
2518
- }
2519
- // there is a loaderPromise, so we are in the middle of a load
2520
- else if (prevMatch.loaderPromise) {
2521
- // do not block if we already have stale data we can show
2522
- // but only if the ongoing load is not a preload since error handling is different for preloads
2523
- // and we don't want to swallow errors
2524
- if (
2525
- prevMatch.status === 'success' &&
2526
- !sync &&
2527
- !prevMatch.preload
2528
- ) {
2529
- return this.getMatch(matchId)!
2530
- }
2531
- await prevMatch.loaderPromise
2532
- const match = this.getMatch(matchId)!
2533
- if (match.error) {
2534
- handleRedirectAndNotFound(match, match.error)
2535
- }
2536
- } else {
2537
- const parentMatchPromise = matchPromises[index - 1] as any
2538
-
2539
- const getLoaderContext = (): LoaderFnContext => {
2540
- const {
2541
- params,
2542
- loaderDeps,
2543
- abortController,
2544
- context,
2545
- cause,
2546
- } = this.getMatch(matchId)!
2547
-
2548
- const preload = resolvePreload(matchId)
2549
-
2550
- return {
2551
- params,
2552
- deps: loaderDeps,
2553
- preload: !!preload,
2554
- parentMatchPromise,
2555
- abortController: abortController,
2556
- context,
2557
- location,
2558
- navigate: (opts) =>
2559
- this.navigate({ ...opts, _fromLocation: location }),
2560
- cause: preload ? 'preload' : cause,
2561
- route,
2562
- }
2563
- }
2564
-
2565
- // This is where all of the stale-while-revalidate magic happens
2566
- const age = Date.now() - this.getMatch(matchId)!.updatedAt
2567
-
2568
- const preload = resolvePreload(matchId)
2569
-
2570
- const staleAge = preload
2571
- ? (route.options.preloadStaleTime ??
2572
- this.options.defaultPreloadStaleTime ??
2573
- 30_000) // 30 seconds for preloads by default
2574
- : (route.options.staleTime ??
2575
- this.options.defaultStaleTime ??
2576
- 0)
2577
-
2578
- const shouldReloadOption = route.options.shouldReload
2579
-
2580
- // Default to reloading the route all the time
2581
- // Allow shouldReload to get the last say,
2582
- // if provided.
2583
- const shouldReload =
2584
- typeof shouldReloadOption === 'function'
2585
- ? shouldReloadOption(getLoaderContext())
2586
- : shouldReloadOption
2587
-
2588
- updateMatch(matchId, (prev) => ({
2589
- ...prev,
2590
- loaderPromise: createControlledPromise<void>(),
2591
- preload:
2592
- !!preload &&
2593
- !this.state.matches.some((d) => d.id === matchId),
2594
- }))
2595
-
2596
- const runLoader = async () => {
2597
- try {
2598
- // If the Matches component rendered
2599
- // the pending component and needs to show it for
2600
- // a minimum duration, we''ll wait for it to resolve
2601
- // before committing to the match and resolving
2602
- // the loadPromise
2603
-
2604
- // Actually run the loader and handle the result
2605
- try {
2606
- if (
2607
- !this.isServer ||
2608
- (this.isServer &&
2609
- this.getMatch(matchId)!.ssr === true)
2610
- ) {
2611
- this.loadRouteChunk(route)
2612
- }
2613
-
2614
- updateMatch(matchId, (prev) => ({
2615
- ...prev,
2616
- isFetching: 'loader',
2617
- }))
2618
-
2619
- // Kick off the loader!
2620
- const loaderData =
2621
- await route.options.loader?.(getLoaderContext())
2622
-
2623
- handleRedirectAndNotFound(
2624
- this.getMatch(matchId)!,
2625
- loaderData,
2626
- )
2627
- updateMatch(matchId, (prev) => ({
2628
- ...prev,
2629
- loaderData,
2630
- }))
2631
-
2632
- // Lazy option can modify the route options,
2633
- // so we need to wait for it to resolve before
2634
- // we can use the options
2635
- await route._lazyPromise
2636
- const head = await executeHead()
2637
- await potentialPendingMinPromise()
2638
-
2639
- // Last but not least, wait for the the components
2640
- // to be preloaded before we resolve the match
2641
- await route._componentsPromise
2642
- updateMatch(matchId, (prev) => ({
2643
- ...prev,
2644
- error: undefined,
2645
- status: 'success',
2646
- isFetching: false,
2647
- updatedAt: Date.now(),
2648
- ...head,
2649
- }))
2650
- } catch (e) {
2651
- let error = e
2652
-
2653
- await potentialPendingMinPromise()
2654
-
2655
- handleRedirectAndNotFound(this.getMatch(matchId)!, e)
2656
-
2657
- try {
2658
- route.options.onError?.(e)
2659
- } catch (onErrorError) {
2660
- error = onErrorError
2661
- handleRedirectAndNotFound(
2662
- this.getMatch(matchId)!,
2663
- onErrorError,
2664
- )
2665
- }
2666
- const head = await executeHead()
2667
- updateMatch(matchId, (prev) => ({
2668
- ...prev,
2669
- error,
2670
- status: 'error',
2671
- isFetching: false,
2672
- ...head,
2673
- }))
2674
- }
2675
- } catch (err) {
2676
- const head = await executeHead()
2677
-
2678
- updateMatch(matchId, (prev) => ({
2679
- ...prev,
2680
- loaderPromise: undefined,
2681
- ...head,
2682
- }))
2683
- handleRedirectAndNotFound(this.getMatch(matchId)!, err)
2684
- }
2685
- }
2686
-
2687
- // If the route is successful and still fresh, just resolve
2688
- const { status, invalid } = this.getMatch(matchId)!
2689
- loaderShouldRunAsync =
2690
- status === 'success' &&
2691
- (invalid || (shouldReload ?? age > staleAge))
2692
- if (preload && route.options.preload === false) {
2693
- // Do nothing
2694
- } else if (loaderShouldRunAsync && !sync) {
2695
- loaderIsRunningAsync = true
2696
- ;(async () => {
2697
- try {
2698
- await runLoader()
2699
- const { loaderPromise, loadPromise } =
2700
- this.getMatch(matchId)!
2701
- loaderPromise?.resolve()
2702
- loadPromise?.resolve()
2703
- updateMatch(matchId, (prev) => ({
2704
- ...prev,
2705
- loaderPromise: undefined,
2706
- }))
2707
- } catch (err) {
2708
- if (isRedirect(err)) {
2709
- await this.navigate(err.options)
2710
- }
2711
- }
2712
- })()
2713
- } else if (
2714
- status !== 'success' ||
2715
- (loaderShouldRunAsync && sync)
2716
- ) {
2717
- await runLoader()
2718
- } else {
2719
- // if the loader did not run, still update head.
2720
- // reason: parent's beforeLoad may have changed the route context
2721
- // and only now do we know the route context (and that the loader would not run)
2722
- const head = await executeHead()
2723
- updateMatch(matchId, (prev) => ({
2724
- ...prev,
2725
- ...head,
2726
- }))
2727
- }
2728
- }
2729
- if (!loaderIsRunningAsync) {
2730
- const { loaderPromise, loadPromise } =
2731
- this.getMatch(matchId)!
2732
- loaderPromise?.resolve()
2733
- loadPromise?.resolve()
2734
- }
2735
-
2736
- updateMatch(matchId, (prev) => {
2737
- clearTimeout(prev.pendingTimeout)
2738
- return {
2739
- ...prev,
2740
- isFetching: loaderIsRunningAsync
2741
- ? prev.isFetching
2742
- : false,
2743
- loaderPromise: loaderIsRunningAsync
2744
- ? prev.loaderPromise
2745
- : undefined,
2746
- invalid: false,
2747
- pendingTimeout: undefined,
2748
- _dehydrated: undefined,
2749
- }
2750
- })
2751
- return this.getMatch(matchId)!
2752
- })(),
2753
- )
2754
- })
2755
-
2756
- await Promise.all(matchPromises)
2757
-
2758
- resolveAll()
2759
- } catch (err) {
2760
- rejectAll(err)
2761
- }
2762
- })()
2763
- })
2764
- await triggerOnReady()
2765
- } catch (err) {
2766
- if (isRedirect(err) || isNotFound(err)) {
2767
- if (isNotFound(err) && !allPreload) {
2768
- await triggerOnReady()
2769
- }
2770
-
2771
- throw err
2772
- }
2773
- }
2774
-
2775
- return matches
2776
- }
2777
-
2778
2089
  invalidate: InvalidateFn<
2779
2090
  RouterCore<
2780
2091
  TRouteTree,
@@ -2791,7 +2102,7 @@ export class RouterCore<
2791
2102
  invalid: true,
2792
2103
  ...(opts?.forcePending || d.status === 'error'
2793
2104
  ? ({ status: 'pending', error: undefined } as const)
2794
- : {}),
2105
+ : undefined),
2795
2106
  }
2796
2107
  }
2797
2108
  return d
@@ -2868,36 +2179,7 @@ export class RouterCore<
2868
2179
  this.clearCache({ filter })
2869
2180
  }
2870
2181
 
2871
- loadRouteChunk = (route: AnyRoute) => {
2872
- if (route._lazyPromise === undefined) {
2873
- if (route.lazyFn) {
2874
- route._lazyPromise = route.lazyFn().then((lazyRoute) => {
2875
- // explicitly don't copy over the lazy route's id
2876
- const { id: _id, ...options } = lazyRoute.options
2877
- Object.assign(route.options, options)
2878
- })
2879
- } else {
2880
- route._lazyPromise = Promise.resolve()
2881
- }
2882
- }
2883
-
2884
- // If for some reason lazy resolves more lazy components...
2885
- // We'll wait for that before pre attempt to preload any
2886
- // components themselves.
2887
- if (route._componentsPromise === undefined) {
2888
- route._componentsPromise = route._lazyPromise.then(() =>
2889
- Promise.all(
2890
- componentTypes.map(async (type) => {
2891
- const component = route.options[type]
2892
- if ((component as any)?.preload) {
2893
- await (component as any).preload()
2894
- }
2895
- }),
2896
- ),
2897
- )
2898
- }
2899
- return route._componentsPromise
2900
- }
2182
+ loadRouteChunk = loadRouteChunk
2901
2183
 
2902
2184
  preloadRoute: PreloadRouteFn<
2903
2185
  TRouteTree,
@@ -2937,7 +2219,8 @@ export class RouterCore<
2937
2219
  })
2938
2220
 
2939
2221
  try {
2940
- matches = await this.loadMatches({
2222
+ matches = await loadMatches({
2223
+ router: this,
2941
2224
  matches,
2942
2225
  location: next,
2943
2226
  preload: true,
@@ -3035,68 +2318,6 @@ export class RouterCore<
3035
2318
 
3036
2319
  serverSsr?: ServerSsr
3037
2320
 
3038
- _handleNotFound = (
3039
- matches: Array<AnyRouteMatch>,
3040
- err: NotFoundError,
3041
- {
3042
- updateMatch = this.updateMatch,
3043
- }: {
3044
- updateMatch?: (
3045
- id: string,
3046
- updater: (match: AnyRouteMatch) => AnyRouteMatch,
3047
- ) => void
3048
- } = {},
3049
- ) => {
3050
- // Find the route that should handle the not found error
3051
- // First check if a specific route is requested to show the error
3052
- const routeCursor = this.routesById[err.routeId ?? ''] ?? this.routeTree
3053
- const matchesByRouteId: Record<string, AnyRouteMatch> = {}
3054
-
3055
- // Setup routesByRouteId object for quick access
3056
- for (const match of matches) {
3057
- matchesByRouteId[match.routeId] = match
3058
- }
3059
-
3060
- // Ensure a NotFoundComponent exists on the route
3061
- if (
3062
- !routeCursor.options.notFoundComponent &&
3063
- (this.options as any)?.defaultNotFoundComponent
3064
- ) {
3065
- routeCursor.options.notFoundComponent = (
3066
- this.options as any
3067
- ).defaultNotFoundComponent
3068
- }
3069
-
3070
- // Ensure we have a notFoundComponent
3071
- invariant(
3072
- routeCursor.options.notFoundComponent,
3073
- 'No notFoundComponent found. Please set a notFoundComponent on your route or provide a defaultNotFoundComponent to the router.',
3074
- )
3075
-
3076
- // Find the match for this route
3077
- const matchForRoute = matchesByRouteId[routeCursor.id]
3078
-
3079
- invariant(
3080
- matchForRoute,
3081
- 'Could not find match for route: ' + routeCursor.id,
3082
- )
3083
-
3084
- // Assign the error to the match - using non-null assertion since we've checked with invariant
3085
- updateMatch(matchForRoute.id, (prev) => ({
3086
- ...prev,
3087
- status: 'notFound',
3088
- error: err,
3089
- isFetching: false,
3090
- }))
3091
-
3092
- if ((err as any).routerCode === 'BEFORE_LOAD' && routeCursor.parentRoute) {
3093
- err.routeId = routeCursor.parentRoute.id
3094
- this._handleNotFound(matches, err, {
3095
- updateMatch,
3096
- })
3097
- }
3098
- }
3099
-
3100
2321
  hasNotFoundMatch = () => {
3101
2322
  return this.__store.state.matches.some(
3102
2323
  (d) => d.status === 'notFound' || d.globalNotFound,
@@ -3174,22 +2395,6 @@ function validateSearch(validateSearch: AnyValidator, input: unknown): unknown {
3174
2395
  return {}
3175
2396
  }
3176
2397
 
3177
- export const componentTypes = [
3178
- 'component',
3179
- 'errorComponent',
3180
- 'pendingComponent',
3181
- 'notFoundComponent',
3182
- ] as const
3183
-
3184
- function routeNeedsPreload(route: AnyRoute) {
3185
- for (const componentType of componentTypes) {
3186
- if ((route.options[componentType] as any)?.preload) {
3187
- return true
3188
- }
3189
- }
3190
- return false
3191
- }
3192
-
3193
2398
  interface RouteLike {
3194
2399
  id: string
3195
2400
  isRoot?: boolean
@@ -3562,7 +2767,8 @@ function applySearchMiddleware({
3562
2767
  try {
3563
2768
  const validatedSearch = {
3564
2769
  ...result,
3565
- ...(validateSearch(route.options.validateSearch, result) ?? {}),
2770
+ ...(validateSearch(route.options.validateSearch, result) ??
2771
+ undefined),
3566
2772
  }
3567
2773
  return validatedSearch
3568
2774
  } catch {