@tanstack/router-core 1.163.3 → 1.166.2

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.
@@ -29,13 +29,12 @@ type InnerLoadContext = {
29
29
  firstBadMatchIndex?: number
30
30
  /** mutable state, scoped to a `loadMatches` call */
31
31
  rendered?: boolean
32
+ serialError?: unknown
32
33
  updateMatch: UpdateMatchFn
33
34
  matches: Array<AnyRouteMatch>
34
35
  preload?: boolean
35
36
  onReady?: () => Promise<void>
36
37
  sync?: boolean
37
- /** mutable state, scoped to a `loadMatches` call */
38
- matchPromises: Array<Promise<AnyRouteMatch>>
39
38
  }
40
39
 
41
40
  const triggerOnReady = (inner: InnerLoadContext): void | Promise<void> => {
@@ -74,64 +73,45 @@ const buildMatchContext = (
74
73
  return context
75
74
  }
76
75
 
77
- const _handleNotFound = (
76
+ const getNotFoundBoundaryIndex = (
78
77
  inner: InnerLoadContext,
79
78
  err: NotFoundError,
80
- routerCode?: string,
81
- ) => {
82
- // Find the route that should handle the not found error
83
- // First check if a specific route is requested to show the error
84
- const routeCursor =
85
- inner.router.routesById[err.routeId ?? ''] ?? inner.router.routeTree
86
-
87
- // Ensure a NotFoundComponent exists on the route
88
- if (
89
- !routeCursor.options.notFoundComponent &&
90
- (inner.router.options as any)?.defaultNotFoundComponent
91
- ) {
92
- routeCursor.options.notFoundComponent = (
93
- inner.router.options as any
94
- ).defaultNotFoundComponent
79
+ ): number | undefined => {
80
+ if (!inner.matches.length) {
81
+ return undefined
95
82
  }
96
83
 
97
- // For BEFORE_LOAD errors that will walk up to a parent route,
98
- // don't require notFoundComponent on the current (child) route —
99
- // an ancestor will handle it. Only enforce the invariant when
100
- // we've reached a route that won't walk up further.
101
- const willWalkUp = routerCode === 'BEFORE_LOAD' && routeCursor.parentRoute
102
-
103
- if (!willWalkUp) {
104
- // Ensure we have a notFoundComponent
105
- invariant(
106
- routeCursor.options.notFoundComponent,
107
- 'No notFoundComponent found. Please set a notFoundComponent on your route or provide a defaultNotFoundComponent to the router.',
108
- )
109
- }
110
-
111
- // Find the match for this route
112
- const matchForRoute = inner.matches.find((m) => m.routeId === routeCursor.id)
84
+ const requestedRouteId = err.routeId
85
+ const matchedRootIndex = inner.matches.findIndex(
86
+ (m) => m.routeId === inner.router.routeTree.id,
87
+ )
88
+ const rootIndex = matchedRootIndex >= 0 ? matchedRootIndex : 0
113
89
 
114
- invariant(matchForRoute, 'Could not find match for route: ' + routeCursor.id)
90
+ let startIndex = requestedRouteId
91
+ ? inner.matches.findIndex((match) => match.routeId === requestedRouteId)
92
+ : (inner.firstBadMatchIndex ?? inner.matches.length - 1)
115
93
 
116
- // Assign the error to the match - using non-null assertion since we've checked with invariant
117
- inner.updateMatch(matchForRoute.id, (prev) => ({
118
- ...prev,
119
- status: 'notFound',
120
- error: err,
121
- isFetching: false,
122
- }))
94
+ if (startIndex < 0) {
95
+ startIndex = rootIndex
96
+ }
123
97
 
124
- if (willWalkUp) {
125
- err.routeId = routeCursor.parentRoute!.id
126
- _handleNotFound(inner, err, routerCode)
98
+ for (let i = startIndex; i >= 0; i--) {
99
+ const match = inner.matches[i]!
100
+ const route = inner.router.looseRoutesById[match.routeId]!
101
+ if (route.options.notFoundComponent) {
102
+ return i
103
+ }
127
104
  }
105
+
106
+ // If no boundary component is found, preserve explicit routeId targeting behavior,
107
+ // otherwise default to root for untargeted notFounds.
108
+ return requestedRouteId ? startIndex : rootIndex
128
109
  }
129
110
 
130
111
  const handleRedirectAndNotFound = (
131
112
  inner: InnerLoadContext,
132
113
  match: AnyRouteMatch | undefined,
133
114
  err: unknown,
134
- routerCode?: string,
135
115
  ): void => {
136
116
  if (!isRedirect(err) && !isNotFound(err)) return
137
117
 
@@ -146,19 +126,26 @@ const handleRedirectAndNotFound = (
146
126
  match._nonReactive.beforeLoadPromise = undefined
147
127
  match._nonReactive.loaderPromise = undefined
148
128
 
149
- const status = isRedirect(err) ? 'redirected' : 'notFound'
150
-
151
129
  match._nonReactive.error = err
152
130
 
153
131
  inner.updateMatch(match.id, (prev) => ({
154
132
  ...prev,
155
- status,
133
+ status: isRedirect(err)
134
+ ? 'redirected'
135
+ : prev.status === 'pending'
136
+ ? 'success'
137
+ : prev.status,
156
138
  context: buildMatchContext(inner, match.index),
157
139
  isFetching: false,
158
140
  error: err,
159
141
  }))
160
142
 
161
143
  if (isNotFound(err) && !err.routeId) {
144
+ // Stamp the throwing match's routeId so that the finalization step in
145
+ // loadMatches knows where the notFound originated. The actual boundary
146
+ // resolution (walking up to the nearest notFoundComponent) is deferred to
147
+ // the finalization step, where firstBadMatchIndex is stable and
148
+ // headMaxIndex can be capped correctly.
162
149
  err.routeId = match.routeId
163
150
  }
164
151
 
@@ -170,11 +157,9 @@ const handleRedirectAndNotFound = (
170
157
  err.options._fromLocation = inner.location
171
158
  err.redirectHandled = true
172
159
  err = inner.router.resolveRedirect(err)
173
- throw err
174
- } else {
175
- _handleNotFound(inner, err, routerCode)
176
- throw err
177
160
  }
161
+
162
+ throw err
178
163
  }
179
164
 
180
165
  const shouldSkipLoader = (
@@ -212,23 +197,13 @@ const handleSerialError = (
212
197
 
213
198
  err.routerCode = routerCode
214
199
  inner.firstBadMatchIndex ??= index
215
- handleRedirectAndNotFound(
216
- inner,
217
- inner.router.getMatch(matchId),
218
- err,
219
- routerCode,
220
- )
200
+ handleRedirectAndNotFound(inner, inner.router.getMatch(matchId), err)
221
201
 
222
202
  try {
223
203
  route.options.onError?.(err)
224
204
  } catch (errorHandlerErr) {
225
205
  err = errorHandlerErr
226
- handleRedirectAndNotFound(
227
- inner,
228
- inner.router.getMatch(matchId),
229
- err,
230
- routerCode,
231
- )
206
+ handleRedirectAndNotFound(inner, inner.router.getMatch(matchId), err)
232
207
  }
233
208
 
234
209
  inner.updateMatch(matchId, (prev) => {
@@ -245,6 +220,10 @@ const handleSerialError = (
245
220
  abortController: new AbortController(),
246
221
  }
247
222
  })
223
+
224
+ if (!inner.preload && !isRedirect(err) && !isNotFound(err)) {
225
+ inner.serialError ??= err
226
+ }
248
227
  }
249
228
 
250
229
  const isBeforeLoadSsr = (
@@ -606,11 +585,12 @@ const executeHead = (
606
585
 
607
586
  const getLoaderContext = (
608
587
  inner: InnerLoadContext,
588
+ matchPromises: Array<Promise<AnyRouteMatch>>,
609
589
  matchId: string,
610
590
  index: number,
611
591
  route: AnyRoute,
612
592
  ): LoaderFnContext => {
613
- const parentMatchPromise = inner.matchPromises[index - 1] as any
593
+ const parentMatchPromise = matchPromises[index - 1] as any
614
594
  const { params, loaderDeps, abortController, cause } =
615
595
  inner.router.getMatch(matchId)!
616
596
 
@@ -639,6 +619,7 @@ const getLoaderContext = (
639
619
 
640
620
  const runLoader = async (
641
621
  inner: InnerLoadContext,
622
+ matchPromises: Array<Promise<AnyRouteMatch>>,
642
623
  matchId: string,
643
624
  index: number,
644
625
  route: AnyRoute,
@@ -660,7 +641,7 @@ const runLoader = async (
660
641
 
661
642
  // Kick off the loader!
662
643
  const loaderResult = route.options.loader?.(
663
- getLoaderContext(inner, matchId, index, route),
644
+ getLoaderContext(inner, matchPromises, matchId, index, route),
664
645
  )
665
646
  const loaderResultIsPromise =
666
647
  route.options.loader && isPromise(loaderResult)
@@ -775,6 +756,7 @@ const runLoader = async (
775
756
 
776
757
  const loadRouteMatch = async (
777
758
  inner: InnerLoadContext,
759
+ matchPromises: Array<Promise<AnyRouteMatch>>,
778
760
  index: number,
779
761
  ): Promise<AnyRouteMatch> => {
780
762
  async function handleLoader(
@@ -798,7 +780,9 @@ const loadRouteMatch = async (
798
780
  // if provided.
799
781
  const shouldReload =
800
782
  typeof shouldReloadOption === 'function'
801
- ? shouldReloadOption(getLoaderContext(inner, matchId, index, route))
783
+ ? shouldReloadOption(
784
+ getLoaderContext(inner, matchPromises, matchId, index, route),
785
+ )
802
786
  : shouldReloadOption
803
787
 
804
788
  // If the route is successful and still fresh, just resolve
@@ -811,7 +795,7 @@ const loadRouteMatch = async (
811
795
  loaderIsRunningAsync = true
812
796
  ;(async () => {
813
797
  try {
814
- await runLoader(inner, matchId, index, route)
798
+ await runLoader(inner, matchPromises, matchId, index, route)
815
799
  const match = inner.router.getMatch(matchId)!
816
800
  match._nonReactive.loaderPromise?.resolve()
817
801
  match._nonReactive.loadPromise?.resolve()
@@ -823,7 +807,7 @@ const loadRouteMatch = async (
823
807
  }
824
808
  })()
825
809
  } else if (status !== 'success' || (loaderShouldRunAsync && inner.sync)) {
826
- await runLoader(inner, matchId, index, route)
810
+ await runLoader(inner, matchPromises, matchId, index, route)
827
811
  }
828
812
  }
829
813
 
@@ -906,9 +890,8 @@ export async function loadMatches(arg: {
906
890
  updateMatch: UpdateMatchFn
907
891
  sync?: boolean
908
892
  }): Promise<Array<MakeRouteMatch>> {
909
- const inner: InnerLoadContext = Object.assign(arg, {
910
- matchPromises: [],
911
- })
893
+ const inner: InnerLoadContext = arg
894
+ const matchPromises: Array<Promise<AnyRouteMatch>> = []
912
895
 
913
896
  // make sure the pending component is immediately rendered when hydrating a match that is not SSRed
914
897
  // the pending component was already rendered on the server and we want to keep it shown on the client until minPendingMs is reached
@@ -919,77 +902,201 @@ export async function loadMatches(arg: {
919
902
  triggerOnReady(inner)
920
903
  }
921
904
 
922
- try {
923
- // Execute all beforeLoads one by one
924
- for (let i = 0; i < inner.matches.length; i++) {
905
+ let beforeLoadNotFound: NotFoundError | undefined
906
+
907
+ // Execute all beforeLoads one by one
908
+ for (let i = 0; i < inner.matches.length; i++) {
909
+ try {
925
910
  const beforeLoad = handleBeforeLoad(inner, i)
926
911
  if (isPromise(beforeLoad)) await beforeLoad
912
+ } catch (err) {
913
+ if (isRedirect(err)) {
914
+ throw err
915
+ }
916
+ if (isNotFound(err)) {
917
+ beforeLoadNotFound = err
918
+ } else {
919
+ if (!inner.preload) throw err
920
+ }
921
+ break
927
922
  }
928
923
 
929
- // Execute all loaders in parallel
930
- const max = inner.firstBadMatchIndex ?? inner.matches.length
931
- for (let i = 0; i < max; i++) {
932
- inner.matchPromises.push(loadRouteMatch(inner, i))
924
+ if (inner.serialError) {
925
+ break
933
926
  }
934
- // Use allSettled to ensure all loaders complete regardless of success/failure
935
- const results = await Promise.allSettled(inner.matchPromises)
936
-
937
- const failures = results
938
- // TODO when we drop support for TS 5.4, we can use the built-in type guard for PromiseRejectedResult
939
- .filter(
940
- (result): result is PromiseRejectedResult =>
941
- result.status === 'rejected',
942
- )
943
- .map((result) => result.reason)
927
+ }
944
928
 
945
- // Find first redirect (throw immediately) or notFound (throw after head execution)
946
- let firstNotFound: unknown
947
- for (const err of failures) {
948
- if (isRedirect(err)) {
949
- throw err
929
+ // Execute loaders once, with max index adapted for beforeLoad notFound handling.
930
+ const baseMaxIndexExclusive = inner.firstBadMatchIndex ?? inner.matches.length
931
+
932
+ const boundaryIndex =
933
+ beforeLoadNotFound && !inner.preload
934
+ ? getNotFoundBoundaryIndex(inner, beforeLoadNotFound)
935
+ : undefined
936
+
937
+ const maxIndexExclusive =
938
+ beforeLoadNotFound && inner.preload
939
+ ? 0
940
+ : boundaryIndex !== undefined
941
+ ? Math.min(boundaryIndex + 1, baseMaxIndexExclusive)
942
+ : baseMaxIndexExclusive
943
+
944
+ let firstNotFound: NotFoundError | undefined
945
+ let firstUnhandledRejection: unknown
946
+
947
+ for (let i = 0; i < maxIndexExclusive; i++) {
948
+ matchPromises.push(loadRouteMatch(inner, matchPromises, i))
949
+ }
950
+
951
+ try {
952
+ await Promise.all(matchPromises)
953
+ } catch {
954
+ const settled = await Promise.allSettled(matchPromises)
955
+
956
+ for (const result of settled) {
957
+ if (result.status !== 'rejected') continue
958
+
959
+ const reason = result.reason
960
+ if (isRedirect(reason)) {
961
+ throw reason
950
962
  }
951
- if (!firstNotFound && isNotFound(err)) {
952
- firstNotFound = err
963
+ if (isNotFound(reason)) {
964
+ firstNotFound ??= reason
965
+ } else {
966
+ firstUnhandledRejection ??= reason
953
967
  }
954
968
  }
955
969
 
956
- // serially execute head functions after all loaders have completed (successfully or not)
957
- // Each head execution is wrapped in try-catch to ensure all heads run even if one fails
958
- for (const match of inner.matches) {
959
- const { id: matchId, routeId } = match
960
- const route = inner.router.looseRoutesById[routeId]!
961
- try {
962
- const headResult = executeHead(inner, matchId, route)
963
- if (headResult) {
964
- const head = await headResult
965
- inner.updateMatch(matchId, (prev) => ({
966
- ...prev,
967
- ...head,
968
- }))
969
- }
970
- } catch (err) {
971
- // Log error but continue executing other head functions
972
- console.error(`Error executing head for route ${routeId}:`, err)
973
- }
970
+ if (firstUnhandledRejection !== undefined) {
971
+ throw firstUnhandledRejection
974
972
  }
973
+ }
974
+
975
+ const notFoundToThrow =
976
+ firstNotFound ??
977
+ (beforeLoadNotFound && !inner.preload ? beforeLoadNotFound : undefined)
978
+
979
+ let headMaxIndex = inner.serialError
980
+ ? (inner.firstBadMatchIndex ?? 0)
981
+ : inner.matches.length - 1
982
+
983
+ if (!notFoundToThrow && beforeLoadNotFound && inner.preload) {
984
+ return inner.matches
985
+ }
986
+
987
+ if (notFoundToThrow) {
988
+ // Determine once which matched route will actually render the
989
+ // notFoundComponent, then pass this precomputed index through the remaining
990
+ // finalization steps.
991
+ // This can differ from the throwing route when routeId targets an ancestor
992
+ // boundary (or when bubbling resolves to a parent/root boundary).
993
+ const renderedBoundaryIndex = getNotFoundBoundaryIndex(
994
+ inner,
995
+ notFoundToThrow,
996
+ )
997
+
998
+ invariant(
999
+ renderedBoundaryIndex !== undefined,
1000
+ 'Could not find match for notFound boundary',
1001
+ )
1002
+ const boundaryMatch = inner.matches[renderedBoundaryIndex]!
1003
+
1004
+ const boundaryRoute = inner.router.looseRoutesById[boundaryMatch.routeId]!
1005
+ const defaultNotFoundComponent = (inner.router.options as any)
1006
+ ?.defaultNotFoundComponent
975
1007
 
976
- // Throw notFound after head execution
977
- if (firstNotFound) {
978
- throw firstNotFound
1008
+ // Ensure a notFoundComponent exists on the boundary route
1009
+ if (!boundaryRoute.options.notFoundComponent && defaultNotFoundComponent) {
1010
+ boundaryRoute.options.notFoundComponent = defaultNotFoundComponent
979
1011
  }
980
1012
 
981
- const readyPromise = triggerOnReady(inner)
982
- if (isPromise(readyPromise)) await readyPromise
983
- } catch (err) {
984
- if (isNotFound(err) && !inner.preload) {
985
- const readyPromise = triggerOnReady(inner)
986
- if (isPromise(readyPromise)) await readyPromise
987
- throw err
1013
+ notFoundToThrow.routeId = boundaryMatch.routeId
1014
+
1015
+ const boundaryIsRoot = boundaryMatch.routeId === inner.router.routeTree.id
1016
+
1017
+ inner.updateMatch(boundaryMatch.id, (prev) => ({
1018
+ ...prev,
1019
+ ...(boundaryIsRoot
1020
+ ? // For root boundary, use globalNotFound so the root component's
1021
+ // shell still renders and <Outlet> handles the not-found display,
1022
+ // instead of replacing the entire root shell via status='notFound'.
1023
+ { status: 'success' as const, globalNotFound: true, error: undefined }
1024
+ : // For non-root boundaries, set status:'notFound' so MatchInner
1025
+ // renders the notFoundComponent directly.
1026
+ { status: 'notFound' as const, error: notFoundToThrow }),
1027
+ isFetching: false,
1028
+ }))
1029
+
1030
+ headMaxIndex = renderedBoundaryIndex
1031
+
1032
+ // Ensure the rendering boundary route chunk (and its lazy components, including
1033
+ // lazy notFoundComponent) is loaded before we continue to head execution/render.
1034
+ await loadRouteChunk(boundaryRoute)
1035
+ } else if (!inner.preload) {
1036
+ // Clear stale root global-not-found state on normal navigations that do not
1037
+ // throw notFound. This must live here (instead of only in runLoader success)
1038
+ // because the root loader may be skipped when data is still fresh.
1039
+ const rootMatch = inner.matches[0]!
1040
+ // `rootMatch` is the next match for this navigation. If it is not global
1041
+ // not-found, then any currently stored root global-not-found is stale.
1042
+ if (!rootMatch.globalNotFound) {
1043
+ // `currentRootMatch` is the current store state (from the previous
1044
+ // navigation/load). Update only when a stale flag is actually present.
1045
+ const currentRootMatch = inner.router.getMatch(rootMatch.id)
1046
+ if (currentRootMatch?.globalNotFound) {
1047
+ inner.updateMatch(rootMatch.id, (prev) => ({
1048
+ ...prev,
1049
+ globalNotFound: false,
1050
+ error: undefined,
1051
+ }))
1052
+ }
988
1053
  }
989
- if (isRedirect(err)) {
990
- throw err
1054
+ }
1055
+
1056
+ // When a serial error occurred (e.g. beforeLoad threw a regular Error),
1057
+ // the erroring route's lazy chunk wasn't loaded because loaders were skipped.
1058
+ // We need to load it so the code-split errorComponent is available for rendering.
1059
+ if (inner.serialError && inner.firstBadMatchIndex !== undefined) {
1060
+ const errorRoute =
1061
+ inner.router.looseRoutesById[
1062
+ inner.matches[inner.firstBadMatchIndex]!.routeId
1063
+ ]!
1064
+ await loadRouteChunk(errorRoute)
1065
+ }
1066
+
1067
+ // serially execute heads once after loaders/notFound handling, ensuring
1068
+ // all head functions get a chance even if one throws.
1069
+ for (let i = 0; i <= headMaxIndex; i++) {
1070
+ const match = inner.matches[i]!
1071
+ const { id: matchId, routeId } = match
1072
+ const route = inner.router.looseRoutesById[routeId]!
1073
+ try {
1074
+ const headResult = executeHead(inner, matchId, route)
1075
+ if (headResult) {
1076
+ const head = await headResult
1077
+ inner.updateMatch(matchId, (prev) => ({
1078
+ ...prev,
1079
+ ...head,
1080
+ }))
1081
+ }
1082
+ } catch (err) {
1083
+ console.error(`Error executing head for route ${routeId}:`, err)
991
1084
  }
992
1085
  }
1086
+
1087
+ const readyPromise = triggerOnReady(inner)
1088
+ if (isPromise(readyPromise)) {
1089
+ await readyPromise
1090
+ }
1091
+
1092
+ if (notFoundToThrow) {
1093
+ throw notFoundToThrow
1094
+ }
1095
+
1096
+ if (inner.serialError && !inner.preload && !inner.onReady) {
1097
+ throw inner.serialError
1098
+ }
1099
+
993
1100
  return inner.matches
994
1101
  }
995
1102
 
@@ -28,6 +28,14 @@ function hydrateMatch(
28
28
  match.ssr = deyhydratedMatch.ssr
29
29
  match.updatedAt = deyhydratedMatch.u
30
30
  match.error = deyhydratedMatch.e
31
+ // Only hydrate global-not-found when a defined value is present in the
32
+ // dehydrated payload. If omitted, preserve the value computed from the
33
+ // current client location (important for SPA fallback HTML served at unknown
34
+ // URLs, where dehydrated matches may come from `/` but client matching marks
35
+ // root as globalNotFound).
36
+ if (deyhydratedMatch.g !== undefined) {
37
+ match.globalNotFound = deyhydratedMatch.g
38
+ }
31
39
  }
32
40
 
33
41
  export async function hydrate(router: AnyRouter): Promise<any> {
@@ -82,10 +90,9 @@ export async function hydrate(router: AnyRouter): Promise<any> {
82
90
 
83
91
  // kick off loading the route chunks
84
92
  const routeChunkPromise = Promise.all(
85
- matches.map((match) => {
86
- const route = router.looseRoutesById[match.routeId]!
87
- return router.loadRouteChunk(route)
88
- }),
93
+ matches.map((match) =>
94
+ router.loadRouteChunk(router.looseRoutesById[match.routeId]!),
95
+ ),
89
96
  )
90
97
 
91
98
  function setMatchForcePending(match: AnyRouteMatch) {
@@ -146,12 +153,10 @@ export async function hydrate(router: AnyRouter): Promise<any> {
146
153
  }
147
154
  })
148
155
 
149
- router.__store.setState((s) => {
150
- return {
151
- ...s,
152
- matches,
153
- }
154
- })
156
+ router.__store.setState((s) => ({
157
+ ...s,
158
+ matches,
159
+ }))
155
160
 
156
161
  // Allow the user to handle custom hydration data
157
162
  await router.options.hydrate?.(dehydratedData)
@@ -277,13 +282,11 @@ export async function hydrate(router: AnyRouter): Promise<any> {
277
282
  }))
278
283
  }
279
284
  // hide the pending component once the load is finished
280
- router.updateMatch(match.id, (prev) => {
281
- return {
282
- ...prev,
283
- _displayPending: undefined,
284
- displayPendingPromise: undefined,
285
- }
286
- })
285
+ router.updateMatch(match.id, (prev) => ({
286
+ ...prev,
287
+ _displayPending: undefined,
288
+ displayPendingPromise: undefined,
289
+ }))
287
290
  })
288
291
  })
289
292
  }
@@ -54,6 +54,9 @@ export function dehydrateMatch(match: AnyRouteMatch): DehydratedMatch {
54
54
  dehydratedMatch[shorthand] = match[key]
55
55
  }
56
56
  }
57
+ if (match.globalNotFound) {
58
+ dehydratedMatch.g = true
59
+ }
57
60
  return dehydratedMatch
58
61
  }
59
62
 
package/src/ssr/types.ts CHANGED
@@ -9,6 +9,7 @@ export interface DehydratedMatch {
9
9
  u: MakeRouteMatch['updatedAt']
10
10
  s: MakeRouteMatch['status']
11
11
  ssr?: MakeRouteMatch['ssr']
12
+ g?: true
12
13
  }
13
14
 
14
15
  export interface DehydratedRouter {