@tanstack/router-core 1.154.13 → 1.154.14

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/src/router.ts CHANGED
@@ -56,7 +56,7 @@ import type {
56
56
  PickAsRequired,
57
57
  Updater,
58
58
  } from './utils'
59
- import type { MatchSnapshot, ParsedLocation } from './location'
59
+ import type { ParsedLocation } from './location'
60
60
  import type {
61
61
  AnyContext,
62
62
  AnyRoute,
@@ -590,14 +590,6 @@ export interface MatchRoutesOpts {
590
590
  throwOnError?: boolean
591
591
  _buildLocation?: boolean
592
592
  dest?: BuildNextOptions
593
- /** Optional match snapshot hint for fast-path (skips path matching) */
594
- snapshot?: MatchSnapshot
595
- }
596
-
597
- export interface MatchRoutesResult {
598
- matches: Array<AnyRouteMatch>
599
- /** Raw string params extracted from path (before parsing) */
600
- rawParams: Record<string, string>
601
593
  }
602
594
 
603
595
  export type InferRouterContext<TRouteTree extends AnyRoute> =
@@ -710,17 +702,14 @@ export type GetMatchRoutesFn = (pathname: string) => {
710
702
  /** exhaustive params, still in their string form */
711
703
  routeParams: Record<string, string>
712
704
  /** partial params, parsed from routeParams during matching */
713
- parsedParams: Record<string, unknown>
705
+ parsedParams: Record<string, unknown> | undefined
714
706
  foundRoute: AnyRoute | undefined
715
707
  parseError?: unknown
716
708
  }
717
709
 
718
710
  export type EmitFn = (routerEvent: RouterEvent) => void
719
711
 
720
- export type LoadFn = (opts?: {
721
- sync?: boolean
722
- _skipUpdateLatestLocation?: boolean
723
- }) => Promise<void>
712
+ export type LoadFn = (opts?: { sync?: boolean }) => Promise<void>
724
713
 
725
714
  export type CommitLocationFn = ({
726
715
  viewTransition,
@@ -903,6 +892,7 @@ export class RouterCore<
903
892
  tempLocationKey: string | undefined = `${Math.round(
904
893
  Math.random() * 10000000,
905
894
  )}`
895
+ resetNextScroll = true
906
896
  shouldViewTransition?: boolean | ViewTransitionOptions = undefined
907
897
  isViewTransitionTypesSupported?: boolean = undefined
908
898
  subscribers = new Set<RouterListener<RouterEvent>>()
@@ -927,8 +917,6 @@ export class RouterCore<
927
917
  origin?: string
928
918
  latestLocation!: ParsedLocation<FullSearchSchema<TRouteTree>>
929
919
  pendingBuiltLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
930
- /** Session id for cached history snapshots */
931
- private sessionId!: string
932
920
  basepath!: string
933
921
  routeTree!: TRouteTree
934
922
  routesById!: RoutesById<TRouteTree>
@@ -949,11 +937,6 @@ export class RouterCore<
949
937
  TDehydrated
950
938
  >,
951
939
  ) {
952
- this.sessionId =
953
- typeof crypto !== 'undefined' && 'randomUUID' in crypto
954
- ? crypto.randomUUID()
955
- : `${Date.now()}-${Math.random().toString(36).slice(2)}`
956
-
957
940
  this.update({
958
941
  defaultPreloadDelay: 50,
959
942
  defaultPendingMs: 1000,
@@ -1271,70 +1254,42 @@ export class RouterCore<
1271
1254
  search: locationSearchOrOpts,
1272
1255
  } as ParsedLocation,
1273
1256
  opts,
1274
- ).matches
1257
+ )
1275
1258
  }
1276
1259
 
1277
1260
  return this.matchRoutesInternal(pathnameOrNext, locationSearchOrOpts)
1278
- .matches
1279
1261
  }
1280
1262
 
1281
1263
  private matchRoutesInternal(
1282
1264
  next: ParsedLocation,
1283
1265
  opts?: MatchRoutesOpts,
1284
- ): MatchRoutesResult {
1285
- // Fast-path: use snapshot hint if valid
1286
- const snapshot = opts?.snapshot
1287
- const snapshotValid =
1288
- snapshot &&
1289
- snapshot.routeIds.length > 0 &&
1290
- snapshot.routeIds.every((id) => this.routesById[id])
1291
-
1292
- let matchedRoutes: ReadonlyArray<AnyRoute>
1293
- let routeParams: Record<string, string>
1294
- let rawParams: Record<string, string>
1295
- let globalNotFoundRouteId: string | undefined
1296
- let parsedParams: Record<string, unknown>
1297
-
1298
- if (snapshotValid) {
1299
- // Rebuild matched routes from snapshot
1300
- matchedRoutes = snapshot.routeIds.map((id) => this.routesById[id]!)
1301
- routeParams = { ...snapshot.params }
1302
- rawParams = { ...snapshot.params }
1303
- globalNotFoundRouteId = snapshot.globalNotFoundRouteId
1304
- parsedParams = snapshot.parsedParams
1305
- } else {
1306
- // Normal path matching
1307
- const matchedRoutesResult = this.getMatchedRoutes(next.pathname)
1308
- const { foundRoute, routeParams: rp } = matchedRoutesResult
1309
- routeParams = rp
1310
- rawParams = { ...rp } // Capture before routeParams gets modified
1311
- matchedRoutes = matchedRoutesResult.matchedRoutes
1312
- parsedParams = matchedRoutesResult.parsedParams
1266
+ ): Array<AnyRouteMatch> {
1267
+ const matchedRoutesResult = this.getMatchedRoutes(next.pathname)
1268
+ const { foundRoute, routeParams, parsedParams } = matchedRoutesResult
1269
+ let { matchedRoutes } = matchedRoutesResult
1270
+ let isGlobalNotFound = false
1313
1271
 
1314
- let isGlobalNotFound = false
1315
-
1316
- // Check to see if the route needs a 404 entry
1317
- if (
1318
- // If we found a route, and it's not an index route and we have left over path
1319
- foundRoute
1320
- ? foundRoute.path !== '/' && routeParams['**']
1321
- : // Or if we didn't find a route and we have left over path
1322
- trimPathRight(next.pathname)
1323
- ) {
1324
- // If the user has defined an (old) 404 route, use it
1325
- if (this.options.notFoundRoute) {
1326
- matchedRoutes = [...matchedRoutes, this.options.notFoundRoute]
1327
- } else {
1328
- // If there is no routes found during path matching
1329
- isGlobalNotFound = true
1330
- }
1272
+ // Check to see if the route needs a 404 entry
1273
+ if (
1274
+ // If we found a route, and it's not an index route and we have left over path
1275
+ foundRoute
1276
+ ? foundRoute.path !== '/' && routeParams['**']
1277
+ : // Or if we didn't find a route and we have left over path
1278
+ trimPathRight(next.pathname)
1279
+ ) {
1280
+ // If the user has defined an (old) 404 route, use it
1281
+ if (this.options.notFoundRoute) {
1282
+ matchedRoutes = [...matchedRoutes, this.options.notFoundRoute]
1283
+ } else {
1284
+ // If there is no routes found during path matching
1285
+ isGlobalNotFound = true
1331
1286
  }
1332
-
1333
- globalNotFoundRouteId = isGlobalNotFound
1334
- ? findGlobalNotFoundRouteId(this.options.notFoundMode, matchedRoutes)
1335
- : undefined
1336
1287
  }
1337
1288
 
1289
+ const globalNotFoundRouteId = isGlobalNotFound
1290
+ ? findGlobalNotFoundRouteId(this.options.notFoundMode, matchedRoutes)
1291
+ : undefined
1292
+
1338
1293
  const matches: Array<AnyRouteMatch> = []
1339
1294
 
1340
1295
  const getParentContext = (parentMatch?: AnyRouteMatch) => {
@@ -1347,19 +1302,6 @@ export class RouterCore<
1347
1302
  return parentContext
1348
1303
  }
1349
1304
 
1350
- // Check if we can use cached validated searches from snapshot
1351
- // Valid if: snapshot exists, searchStr matches, and validatedSearches has correct length
1352
- const canUseCachedSearch =
1353
- snapshotValid &&
1354
- snapshot.searchStr === next.searchStr &&
1355
- snapshot.validatedSearches?.length === matchedRoutes.length
1356
-
1357
- // Collect validated searches to cache in snapshot (only when not using cache)
1358
- const validatedSearchesToCache: Array<{
1359
- search: Record<string, unknown>
1360
- strictSearch: Record<string, unknown>
1361
- }> = []
1362
-
1363
1305
  matchedRoutes.forEach((route, index) => {
1364
1306
  // Take each matched route and resolve + validate its search params
1365
1307
  // This has to happen serially because each route's search params
@@ -1375,12 +1317,6 @@ export class RouterCore<
1375
1317
  Record<string, any>,
1376
1318
  any,
1377
1319
  ] = (() => {
1378
- // Fast-path: use cached validated search from snapshot
1379
- if (canUseCachedSearch) {
1380
- const cached = snapshot.validatedSearches![index]!
1381
- return [cached.search, cached.strictSearch, undefined]
1382
- }
1383
-
1384
1320
  // Validate the search params and stabilize them
1385
1321
  const parentSearch = parentMatch?.search ?? next.search
1386
1322
  const parentStrictSearch = parentMatch?._strictSearch ?? undefined
@@ -1414,14 +1350,6 @@ export class RouterCore<
1414
1350
  }
1415
1351
  })()
1416
1352
 
1417
- // Cache the validated search for future pop navigations
1418
- if (!canUseCachedSearch) {
1419
- validatedSearchesToCache.push({
1420
- search: preMatchSearch,
1421
- strictSearch: strictMatchSearch,
1422
- })
1423
- }
1424
-
1425
1353
  // This is where we need to call route.options.loaderDeps() to get any additional
1426
1354
  // deps that the route's loader function might need to run. We need to do this
1427
1355
  // before we create the match so that we can pass the deps to the route's
@@ -1588,18 +1516,6 @@ export class RouterCore<
1588
1516
  matches.push(match)
1589
1517
  })
1590
1518
 
1591
- // Cache validated searches in snapshot for future pop navigations
1592
- // Only update if we computed fresh values (not using cached)
1593
- if (!canUseCachedSearch && validatedSearchesToCache.length > 0) {
1594
- const existingSnapshot = next.state?.__TSR_matches as
1595
- | MatchSnapshot
1596
- | undefined
1597
- if (existingSnapshot) {
1598
- existingSnapshot.searchStr = next.searchStr
1599
- existingSnapshot.validatedSearches = validatedSearchesToCache
1600
- }
1601
- }
1602
-
1603
1519
  matches.forEach((match, index) => {
1604
1520
  const route = this.looseRoutesById[match.routeId]!
1605
1521
  const existingMatch = this.getMatch(match.id)
@@ -1639,7 +1555,7 @@ export class RouterCore<
1639
1555
  }
1640
1556
  })
1641
1557
 
1642
- return { matches, rawParams }
1558
+ return matches
1643
1559
  }
1644
1560
 
1645
1561
  getMatchedRoutes: GetMatchRoutesFn = (pathname) => {
@@ -1767,7 +1683,6 @@ export class RouterCore<
1767
1683
  // which are expensive and not needed for buildLocation
1768
1684
  const destMatchResult = this.getMatchedRoutes(interpolatedNextTo)
1769
1685
  let destRoutes = destMatchResult.matchedRoutes
1770
- const rawParams = destMatchResult.routeParams
1771
1686
 
1772
1687
  // Compute globalNotFoundRouteId using the same logic as matchRoutesInternal
1773
1688
  const isGlobalNotFound = destMatchResult.foundRoute
@@ -1867,20 +1782,6 @@ export class RouterCore<
1867
1782
  // Replace the equal deep
1868
1783
  nextState = replaceEqualDeep(currentLocation.state, nextState)
1869
1784
 
1870
- // Build match snapshot for fast-path on back/forward navigation
1871
- // Use raw params captured during matchRoutesInternal (needed for literal path navigation
1872
- // where nextParams may be empty but path contains param values)
1873
- const snapshotParams = {
1874
- ...rawParams,
1875
- ...nextParams,
1876
- }
1877
- const matchSnapshot = buildMatchSnapshotFromRoutes({
1878
- routes: destRoutes,
1879
- params: snapshotParams,
1880
- searchStr,
1881
- globalNotFoundRouteId,
1882
- })
1883
-
1884
1785
  // Create the full path of the location
1885
1786
  const fullPath = `${nextPathname}${searchStr}${hashStr}`
1886
1787
 
@@ -1890,13 +1791,10 @@ export class RouterCore<
1890
1791
  // If a rewrite function is provided, use it to rewrite the URL
1891
1792
  const rewrittenUrl = executeRewriteOutput(this.rewrite, url)
1892
1793
 
1893
- // Use encoded URL path for href (consistent with parseLocation)
1894
- const encodedHref = url.href.replace(url.origin, '')
1895
-
1896
1794
  return {
1897
1795
  publicHref:
1898
1796
  rewrittenUrl.pathname + rewrittenUrl.search + rewrittenUrl.hash,
1899
- href: encodedHref,
1797
+ href: fullPath,
1900
1798
  url: rewrittenUrl,
1901
1799
  pathname: nextPathname,
1902
1800
  search: nextSearch,
@@ -1904,7 +1802,6 @@ export class RouterCore<
1904
1802
  state: nextState as any,
1905
1803
  hash: hash ?? '',
1906
1804
  unmaskOnReload: dest.unmaskOnReload,
1907
- _matchSnapshot: matchSnapshot,
1908
1805
  }
1909
1806
  }
1910
1807
 
@@ -2010,105 +1907,65 @@ export class RouterCore<
2010
1907
  // Don't commit to history if nothing changed
2011
1908
  if (isSameUrl && isSameState()) {
2012
1909
  this.load()
2013
- return this.commitLocationPromise
2014
- }
2015
-
2016
- let {
2017
- // eslint-disable-next-line prefer-const
2018
- maskedLocation,
2019
- // eslint-disable-next-line prefer-const
2020
- hashScrollIntoView,
2021
- // don't pass url into history since it is a URL instance that cannot be serialized
2022
- // eslint-disable-next-line prefer-const
2023
- url: _url,
2024
- ...nextHistory
2025
- } = next
2026
-
2027
- if (maskedLocation) {
2028
- nextHistory = {
2029
- ...maskedLocation,
2030
- state: {
2031
- ...maskedLocation.state,
2032
- __tempKey: undefined,
2033
- __tempLocation: {
2034
- ...nextHistory,
2035
- search: nextHistory.searchStr,
2036
- state: {
2037
- ...nextHistory.state,
2038
- __tempKey: undefined!,
2039
- __tempLocation: undefined!,
2040
- __TSR_key: undefined!,
2041
- key: undefined!, // TODO: Remove in v2 - use __TSR_key instead
1910
+ } else {
1911
+ let {
1912
+ // eslint-disable-next-line prefer-const
1913
+ maskedLocation,
1914
+ // eslint-disable-next-line prefer-const
1915
+ hashScrollIntoView,
1916
+ // don't pass url into history since it is a URL instance that cannot be serialized
1917
+ // eslint-disable-next-line prefer-const
1918
+ url: _url,
1919
+ ...nextHistory
1920
+ } = next
1921
+
1922
+ if (maskedLocation) {
1923
+ nextHistory = {
1924
+ ...maskedLocation,
1925
+ state: {
1926
+ ...maskedLocation.state,
1927
+ __tempKey: undefined,
1928
+ __tempLocation: {
1929
+ ...nextHistory,
1930
+ search: nextHistory.searchStr,
1931
+ state: {
1932
+ ...nextHistory.state,
1933
+ __tempKey: undefined!,
1934
+ __tempLocation: undefined!,
1935
+ __TSR_key: undefined!,
1936
+ key: undefined!, // TODO: Remove in v2 - use __TSR_key instead
1937
+ },
2042
1938
  },
2043
1939
  },
2044
- },
2045
- }
1940
+ }
2046
1941
 
2047
- if (nextHistory.unmaskOnReload ?? this.options.unmaskOnReload ?? false) {
2048
- nextHistory.state.__tempKey = this.tempLocationKey
1942
+ if (
1943
+ nextHistory.unmaskOnReload ??
1944
+ this.options.unmaskOnReload ??
1945
+ false
1946
+ ) {
1947
+ nextHistory.state.__tempKey = this.tempLocationKey
1948
+ }
2049
1949
  }
2050
- }
2051
-
2052
- nextHistory.state.__hashScrollIntoViewOptions =
2053
- hashScrollIntoView ?? this.options.defaultHashScrollIntoView ?? true
2054
-
2055
- // Store resetScroll in history state so it survives back/forward navigation
2056
- nextHistory.state.__TSR_resetScroll = next.resetScroll ?? true
2057
1950
 
2058
- this.shouldViewTransition = viewTransition
1951
+ nextHistory.state.__hashScrollIntoViewOptions =
1952
+ hashScrollIntoView ?? this.options.defaultHashScrollIntoView ?? true
2059
1953
 
2060
- // Store session id for this router lifetime
2061
- nextHistory.state.__TSR_sessionId = this.sessionId
1954
+ this.shouldViewTransition = viewTransition
2062
1955
 
2063
- // Use match snapshot from buildLocation if available, otherwise compute it.
2064
- // Stored in history state for pop/back/forward fast-path.
2065
- nextHistory.state.__TSR_matches =
2066
- next._matchSnapshot ??
2067
- buildMatchSnapshot({
2068
- matchResult: this.getMatchedRoutes(next.pathname),
2069
- pathname: next.pathname,
2070
- searchStr: next.searchStr,
2071
- notFoundRoute: this.options.notFoundRoute,
2072
- notFoundMode: this.options.notFoundMode,
2073
- })
2074
-
2075
- // Build the pre-computed ParsedLocation to avoid re-parsing after push
2076
- // Spread next (which has href, pathname, search, etc.) and override with final state
2077
- const precomputedLocation: ParsedLocation = {
2078
- ...next,
2079
- publicHref: nextHistory.publicHref,
2080
- state: nextHistory.state,
2081
- maskedLocation,
1956
+ this.history[next.replace ? 'replace' : 'push'](
1957
+ nextHistory.publicHref,
1958
+ nextHistory.state,
1959
+ { ignoreBlocker },
1960
+ )
2082
1961
  }
2083
1962
 
2084
- // Await push/replace to handle blockers before proceeding
2085
- // Pass skipTransitionerLoad so Transitioner doesn't call load() - we handle it below
2086
- const result = await this.history[next.replace ? 'replace' : 'push'](
2087
- nextHistory.publicHref,
2088
- nextHistory.state,
2089
- { ignoreBlocker, skipTransitionerLoad: true },
2090
- )
2091
-
2092
- // If blocked, resolve promise and return
2093
- if (result.type === 'BLOCKED') {
2094
- this.commitLocationPromise?.resolve()
2095
- return this.commitLocationPromise
2096
- }
1963
+ this.resetNextScroll = next.resetScroll ?? true
2097
1964
 
2098
- // Check if another navigation has superseded this one while we awaited
2099
- // If so, let the newer navigation handle things - don't overwrite latestLocation
2100
- if (this.history.location.href !== nextHistory.publicHref) {
2101
- return this.commitLocationPromise
1965
+ if (!this.history.subscribers.size) {
1966
+ this.load()
2102
1967
  }
2103
1968
 
2104
- // Success: set latestLocation directly (we skip updateLatestLocation in load)
2105
- this.latestLocation = precomputedLocation as unknown as ParsedLocation<
2106
- FullSearchSchema<TRouteTree>
2107
- >
2108
-
2109
- // Call load() with _skipUpdateLatestLocation since we already set latestLocation
2110
- this.load({ _skipUpdateLatestLocation: true })
2111
-
2112
1969
  return this.commitLocationPromise
2113
1970
  }
2114
1971
 
@@ -2260,14 +2117,10 @@ export class RouterCore<
2260
2117
 
2261
2118
  latestLoadPromise: undefined | Promise<void>
2262
2119
 
2263
- beforeLoad = (opts?: { _skipUpdateLatestLocation?: boolean }) => {
2120
+ beforeLoad = () => {
2264
2121
  // Cancel any pending matches
2265
2122
  this.cancelMatches()
2266
- if (!opts?._skipUpdateLatestLocation) {
2267
- this.updateLatestLocation()
2268
- } else {
2269
- // Already have latestLocation from commitLocation, skip parsing
2270
- }
2123
+ this.updateLatestLocation()
2271
2124
 
2272
2125
  if (this.isServer) {
2273
2126
  // for SPAs on the initial load, this is handled by the Transitioner
@@ -2291,12 +2144,7 @@ export class RouterCore<
2291
2144
  }
2292
2145
 
2293
2146
  // Match the routes
2294
- // Use snapshot from history state for fast-path only within same router lifetime
2295
- const snapshot =
2296
- this.latestLocation.state.__TSR_sessionId === this.sessionId
2297
- ? this.latestLocation.state.__TSR_matches
2298
- : undefined
2299
- const pendingMatches = this.matchRoutes(this.latestLocation, { snapshot })
2147
+ const pendingMatches = this.matchRoutes(this.latestLocation)
2300
2148
 
2301
2149
  // Ingest the new matches
2302
2150
  this.__store.setState((s) => ({
@@ -2313,10 +2161,7 @@ export class RouterCore<
2313
2161
  }))
2314
2162
  }
2315
2163
 
2316
- load: LoadFn = async (opts?: {
2317
- sync?: boolean
2318
- _skipUpdateLatestLocation?: boolean
2319
- }): Promise<void> => {
2164
+ load: LoadFn = async (opts?: { sync?: boolean }): Promise<void> => {
2320
2165
  let redirect: AnyRedirect | undefined
2321
2166
  let notFound: NotFoundError | undefined
2322
2167
  let loadPromise: Promise<void>
@@ -2325,9 +2170,7 @@ export class RouterCore<
2325
2170
  loadPromise = new Promise<void>((resolve) => {
2326
2171
  this.startTransition(async () => {
2327
2172
  try {
2328
- this.beforeLoad({
2329
- _skipUpdateLatestLocation: opts?._skipUpdateLatestLocation,
2330
- })
2173
+ this.beforeLoad()
2331
2174
  const next = this.latestLocation
2332
2175
  const prevLocation = this.state.resolvedLocation
2333
2176
 
@@ -2935,7 +2778,7 @@ export function getMatchedRoutes<TRouteLike extends RouteLike>({
2935
2778
  const trimmedPath = trimPathRight(pathname)
2936
2779
 
2937
2780
  let foundRoute: TRouteLike | undefined = undefined
2938
- let parsedParams: Record<string, unknown> = {}
2781
+ let parsedParams: Record<string, unknown> | undefined = undefined
2939
2782
  const match = findRouteMatch<TRouteLike>(trimmedPath, processedTree, true)
2940
2783
  if (match) {
2941
2784
  foundRoute = match.route
@@ -2948,96 +2791,6 @@ export function getMatchedRoutes<TRouteLike extends RouteLike>({
2948
2791
  return { matchedRoutes, routeParams, foundRoute, parsedParams }
2949
2792
  }
2950
2793
 
2951
- /**
2952
- * Build a MatchSnapshot from a getMatchedRoutes result.
2953
- * Determines globalNotFoundRouteId using the same logic as matchRoutesInternal.
2954
- */
2955
- export function buildMatchSnapshot({
2956
- matchResult,
2957
- pathname,
2958
- searchStr,
2959
- notFoundRoute,
2960
- notFoundMode,
2961
- }: {
2962
- matchResult: ReturnType<typeof getMatchedRoutes>
2963
- pathname: string
2964
- searchStr?: string
2965
- notFoundRoute?: AnyRoute
2966
- notFoundMode?: 'root' | 'fuzzy'
2967
- }): MatchSnapshot {
2968
- const snapshot: MatchSnapshot = {
2969
- routeIds: matchResult.matchedRoutes.map((r) => r.id),
2970
- params: matchResult.routeParams,
2971
- parsedParams: matchResult.parsedParams,
2972
- searchStr,
2973
- }
2974
-
2975
- const isGlobalNotFound = matchResult.foundRoute
2976
- ? matchResult.foundRoute.path !== '/' && matchResult.routeParams['**']
2977
- : trimPathRight(pathname)
2978
-
2979
- if (isGlobalNotFound) {
2980
- if (notFoundRoute) {
2981
- // Custom notFoundRoute provided - use its id
2982
- snapshot.globalNotFoundRouteId = notFoundRoute.id
2983
- } else {
2984
- if (notFoundMode !== 'root') {
2985
- for (let i = matchResult.matchedRoutes.length - 1; i >= 0; i--) {
2986
- const route = matchResult.matchedRoutes[i]!
2987
- if (route.children) {
2988
- snapshot.globalNotFoundRouteId = route.id
2989
- break
2990
- }
2991
- }
2992
- }
2993
- if (!snapshot.globalNotFoundRouteId) {
2994
- snapshot.globalNotFoundRouteId = rootRouteId
2995
- }
2996
- }
2997
- }
2998
-
2999
- return snapshot
3000
- }
3001
-
3002
- /**
3003
- * Build a MatchSnapshot from routes and params directly.
3004
- * Used by buildLocation to avoid duplicate getMatchedRoutes call.
3005
- */
3006
- export function buildMatchSnapshotFromRoutes({
3007
- routes,
3008
- params,
3009
- searchStr,
3010
- globalNotFoundRouteId,
3011
- }: {
3012
- routes: ReadonlyArray<AnyRoute>
3013
- params: Record<string, unknown>
3014
- searchStr?: string
3015
- globalNotFoundRouteId?: string
3016
- }): MatchSnapshot {
3017
- // Convert all params to strings for snapshot storage
3018
- // (params from path matching are always strings)
3019
- const stringParams: Record<string, string> = {}
3020
- for (const key in params) {
3021
- const value = params[key]
3022
- if (value != null) {
3023
- stringParams[key] = String(value)
3024
- }
3025
- }
3026
-
3027
- const snapshot: MatchSnapshot = {
3028
- routeIds: routes.map((r) => r.id),
3029
- params: stringParams,
3030
- parsedParams: params,
3031
- searchStr,
3032
- }
3033
-
3034
- if (globalNotFoundRouteId) {
3035
- snapshot.globalNotFoundRouteId = globalNotFoundRouteId
3036
- }
3037
-
3038
- return snapshot
3039
- }
3040
-
3041
2794
  /**
3042
2795
  * TODO: once caches are persisted across requests on the server,
3043
2796
  * we can cache the built middleware chain using `last(destRoutes)` as the key
@@ -340,8 +340,8 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
340
340
 
341
341
  // If the user doesn't want to restore the scroll position,
342
342
  // we don't need to do anything.
343
- const resetScroll = event.toLocation.state.__TSR_resetScroll ?? true
344
- if (!resetScroll) {
343
+ if (!router.resetNextScroll) {
344
+ router.resetNextScroll = true
345
345
  return
346
346
  }
347
347
  if (typeof router.options.scrollRestoration === 'function') {