@tanstack/router-core 1.166.7 → 1.167.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/router-core",
3
- "version": "1.166.7",
3
+ "version": "1.167.1",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -150,7 +150,7 @@
150
150
  "seroval-plugins": "^1.4.2",
151
151
  "tiny-invariant": "^1.3.3",
152
152
  "tiny-warning": "^1.0.3",
153
- "@tanstack/history": "1.161.4"
153
+ "@tanstack/history": "1.161.5"
154
154
  },
155
155
  "devDependencies": {
156
156
  "esbuild": "^0.25.0",
package/src/index.ts CHANGED
@@ -164,7 +164,9 @@ export type {
164
164
  FileBaseRouteOptions,
165
165
  BaseRouteOptions,
166
166
  UpdatableRouteOptions,
167
+ LoaderStaleReloadMode,
167
168
  RouteLoaderFn,
169
+ RouteLoaderEntry,
168
170
  LoaderFnContext,
169
171
  RouteContextFn,
170
172
  ContextOptions,
@@ -657,11 +657,13 @@ const runLoader = async (
657
657
  }
658
658
 
659
659
  // Kick off the loader!
660
- const loaderResult = route.options.loader?.(
660
+ const routeLoader = route.options.loader
661
+ const loader =
662
+ typeof routeLoader === 'function' ? routeLoader : routeLoader?.handler
663
+ const loaderResult = loader?.(
661
664
  getLoaderContext(inner, matchPromises, matchId, index, route),
662
665
  )
663
- const loaderResultIsPromise =
664
- route.options.loader && isPromise(loaderResult)
666
+ const loaderResultIsPromise = !!loader && isPromise(loaderResult)
665
667
 
666
668
  const willLoadSomething = !!(
667
669
  loaderResultIsPromise ||
@@ -680,7 +682,7 @@ const runLoader = async (
680
682
  }))
681
683
  }
682
684
 
683
- if (route.options.loader) {
685
+ if (loader) {
684
686
  const loaderData = loaderResultIsPromise
685
687
  ? await loaderResult
686
688
  : loaderResult
@@ -820,7 +822,11 @@ const loadRouteMatch = async (
820
822
  (invalid || (shouldReload ?? staleMatchShouldReload))
821
823
  if (preload && route.options.preload === false) {
822
824
  // Do nothing
823
- } else if (loaderShouldRunAsync && !inner.sync) {
825
+ } else if (
826
+ loaderShouldRunAsync &&
827
+ !inner.sync &&
828
+ shouldReloadInBackground
829
+ ) {
824
830
  loaderIsRunningAsync = true
825
831
  ;(async () => {
826
832
  try {
@@ -835,7 +841,7 @@ const loadRouteMatch = async (
835
841
  }
836
842
  }
837
843
  })()
838
- } else if (status !== 'success' || (loaderShouldRunAsync && inner.sync)) {
844
+ } else if (status !== 'success' || loaderShouldRunAsync) {
839
845
  await runLoader(inner, matchPromises, matchId, index, route)
840
846
  } else {
841
847
  syncMatchContext(inner, matchId, index)
@@ -846,6 +852,12 @@ const loadRouteMatch = async (
846
852
  let loaderShouldRunAsync = false
847
853
  let loaderIsRunningAsync = false
848
854
  const route = inner.router.looseRoutesById[routeId]!
855
+ const routeLoader = route.options.loader
856
+ const shouldReloadInBackground =
857
+ ((typeof routeLoader === 'function'
858
+ ? undefined
859
+ : routeLoader?.staleReloadMode) ??
860
+ inner.router.options.defaultStaleReloadMode) !== 'blocking'
849
861
 
850
862
  if (shouldSkipLoader(inner, matchId)) {
851
863
  const match = inner.router.getMatch(matchId)
@@ -871,7 +883,12 @@ const loadRouteMatch = async (
871
883
  // do not block if we already have stale data we can show
872
884
  // but only if the ongoing load is not a preload since error handling is different for preloads
873
885
  // and we don't want to swallow errors
874
- if (prevMatch.status === 'success' && !inner.sync && !prevMatch.preload) {
886
+ if (
887
+ prevMatch.status === 'success' &&
888
+ !inner.sync &&
889
+ !prevMatch.preload &&
890
+ shouldReloadInBackground
891
+ ) {
875
892
  return prevMatch
876
893
  }
877
894
  await prevMatch._nonReactive.loaderPromise
package/src/route.ts CHANGED
@@ -292,11 +292,44 @@ export type ResolveRouteContext<TRouteContextFn, TBeforeLoadFn> = Assign<
292
292
  ContextAsyncReturnType<TBeforeLoadFn>
293
293
  >
294
294
 
295
+ export type ResolveRouteLoaderFn<TLoaderFn> = TLoaderFn extends {
296
+ handler: infer THandler
297
+ }
298
+ ? THandler
299
+ : TLoaderFn
300
+
301
+ export type RouteLoaderObject<
302
+ TRegister,
303
+ TParentRoute extends AnyRoute = AnyRoute,
304
+ TId extends string = string,
305
+ TParams = {},
306
+ TLoaderDeps = {},
307
+ TRouterContext = {},
308
+ TRouteContextFn = AnyContext,
309
+ TBeforeLoadFn = AnyContext,
310
+ TServerMiddlewares = unknown,
311
+ THandlers = undefined,
312
+ > = {
313
+ handler: RouteLoaderFn<
314
+ TRegister,
315
+ TParentRoute,
316
+ TId,
317
+ TParams,
318
+ TLoaderDeps,
319
+ TRouterContext,
320
+ TRouteContextFn,
321
+ TBeforeLoadFn,
322
+ TServerMiddlewares,
323
+ THandlers
324
+ >
325
+ staleReloadMode?: LoaderStaleReloadMode
326
+ }
327
+
295
328
  export type ResolveLoaderData<TLoaderFn> = unknown extends TLoaderFn
296
329
  ? TLoaderFn
297
- : LooseAsyncReturnType<TLoaderFn> extends never
330
+ : LooseAsyncReturnType<ResolveRouteLoaderFn<TLoaderFn>> extends never
298
331
  ? undefined
299
- : LooseAsyncReturnType<TLoaderFn>
332
+ : LooseAsyncReturnType<ResolveRouteLoaderFn<TLoaderFn>>
300
333
 
301
334
  export type ResolveFullSearchSchema<
302
335
  TParentRoute extends AnyRoute,
@@ -1010,8 +1043,7 @@ export interface FilebaseRouteOptionsInterface<
1010
1043
 
1011
1044
  loader?: Constrain<
1012
1045
  TLoaderFn,
1013
- (
1014
- ctx: LoaderFnContext<
1046
+ | RouteLoaderFn<
1015
1047
  TRegister,
1016
1048
  TParentRoute,
1017
1049
  TId,
@@ -1022,13 +1054,19 @@ export interface FilebaseRouteOptionsInterface<
1022
1054
  TBeforeLoadFn,
1023
1055
  TServerMiddlewares,
1024
1056
  THandlers
1025
- >,
1026
- ) => ValidateSerializableLifecycleResult<
1027
- TRegister,
1028
- TParentRoute,
1029
- TSSR,
1030
- TLoaderFn
1031
- >
1057
+ >
1058
+ | RouteLoaderObject<
1059
+ TRegister,
1060
+ TParentRoute,
1061
+ TId,
1062
+ TParams,
1063
+ TLoaderDeps,
1064
+ TRouterContext,
1065
+ TRouteContextFn,
1066
+ TBeforeLoadFn,
1067
+ TServerMiddlewares,
1068
+ THandlers
1069
+ >
1032
1070
  >
1033
1071
  }
1034
1072
 
@@ -1415,6 +1453,45 @@ export type RouteLoaderFn<
1415
1453
  >,
1416
1454
  ) => any
1417
1455
 
1456
+ export type LoaderStaleReloadMode = 'background' | 'blocking'
1457
+
1458
+ export type RouteLoaderEntry<
1459
+ TRegister,
1460
+ TParentRoute extends AnyRoute = AnyRoute,
1461
+ TId extends string = string,
1462
+ TParams = {},
1463
+ TLoaderDeps = {},
1464
+ TRouterContext = {},
1465
+ TRouteContextFn = AnyContext,
1466
+ TBeforeLoadFn = AnyContext,
1467
+ TServerMiddlewares = unknown,
1468
+ THandlers = undefined,
1469
+ > =
1470
+ | RouteLoaderFn<
1471
+ TRegister,
1472
+ TParentRoute,
1473
+ TId,
1474
+ TParams,
1475
+ TLoaderDeps,
1476
+ TRouterContext,
1477
+ TRouteContextFn,
1478
+ TBeforeLoadFn,
1479
+ TServerMiddlewares,
1480
+ THandlers
1481
+ >
1482
+ | RouteLoaderObject<
1483
+ TRegister,
1484
+ TParentRoute,
1485
+ TId,
1486
+ TParams,
1487
+ TLoaderDeps,
1488
+ TRouterContext,
1489
+ TRouteContextFn,
1490
+ TBeforeLoadFn,
1491
+ TServerMiddlewares,
1492
+ THandlers
1493
+ >
1494
+
1418
1495
  export interface LoaderFnContext<
1419
1496
  in out TRegister = unknown,
1420
1497
  in out TParentRoute extends AnyRoute = AnyRoute,
package/src/router.ts CHANGED
@@ -72,6 +72,7 @@ import type {
72
72
  AnyContext,
73
73
  AnyRoute,
74
74
  AnyRouteWithContext,
75
+ LoaderStaleReloadMode,
75
76
  MakeRemountDepsOptionsUnion,
76
77
  RouteContextOptions,
77
78
  RouteLike,
@@ -238,6 +239,15 @@ export interface RouterOptions<
238
239
  * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#key-options)
239
240
  */
240
241
  defaultStaleTime?: number
242
+ /**
243
+ * The default stale reload mode a route loader should use if no `loader.staleReloadMode` is provided.
244
+ *
245
+ * `'background'` preserves the current stale-while-revalidate behavior.
246
+ * `'blocking'` waits for stale loader reloads to complete before resolving navigation.
247
+ *
248
+ * @default 'background'
249
+ */
250
+ defaultStaleReloadMode?: LoaderStaleReloadMode
241
251
  /**
242
252
  * The default `preloadStaleTime` a route should use if no preloadStaleTime is provided.
243
253
  *
@@ -790,6 +800,8 @@ export interface ServerSsr {
790
800
  isDehydrated: () => boolean
791
801
  isSerializationFinished: () => boolean
792
802
  onRenderFinished: (listener: () => void) => void
803
+ setRenderFinished: () => void
804
+ cleanup: () => void
793
805
  onSerializationFinished: (listener: () => void) => void
794
806
  dehydrate: () => Promise<void>
795
807
  takeBufferedScripts: () => RouterManagedTag | undefined