diff --git a/.changeset/lane-match-loader-rewrite.md b/.changeset/lane-match-loader-rewrite.md index 911a9568640..a7d63e6d34c 100644 --- a/.changeset/lane-match-loader-rewrite.md +++ b/.changeset/lane-match-loader-rewrite.md @@ -3,28 +3,33 @@ '@tanstack/react-router': patch '@tanstack/solid-router': patch '@tanstack/vue-router': patch +'@tanstack/router-devtools-core': patch --- Rewrite match loading around a lane-based scheduler that tracks each navigation, preload, and background reload as an ordered unit of work. This fixes pending/redirect/retry state leaking between overlapping navigations, restores correct SSR status codes for redirects, errors, and not-found responses, and closes hydration gaps where the client re-ran work the server had already completed. - Invalidation now retires matching active preloads so older speculative loader results cannot become fresh cache data after invalidation. - Route `headers()` now only runs on the server, matching the documented behavior — it is no longer invoked during client-side asset projection. -- Default `gcTime` and `preloadGcTime` are reduced from 30 minutes (`1_800_000`) to 5 minutes (`300_000`). +- The documented default `gcTime` and `preloadGcTime` now match the existing runtime default of 5 minutes (`300_000`). -**Removed / changed public API** +**Removed / changed exported internals** - `RouterState` no longer includes `loadedAt`, `isTransitioning`, `statusCode`, or `redirect`. Use `match.updatedAt` in place of `loadedAt`; subscribe to `router.state.status` / `router.state.isLoading` in place of `isTransitioning`; server response status and redirect handling are now internal to the server loader and are no longer exposed on `router.state`. - `RouteMatch.fetchCount` has been removed, with no replacement — it was purely informational. - `RouteMatch.status` no longer includes `'redirected'` (it remains `'pending' | 'success' | 'error' | 'notFound'`) — redirected matches are dropped from the match list instead of being rendered. - `RouteMatch.globalNotFound` has been renamed and privatized to the internal `_notFound` field. Use `match.status === 'notFound'` instead. +- The exported React, Solid, and Vue `Match` components now accept `routeId` instead of `matchId`. +- The exported `RouterStores` adapter contract now uses route-keyed presentation stores: `matchesId` is replaced by `ids`, `matchStores` by `byRoute`, and `getRouteMatchStore()` by `getMatchStore()`. The separate `loadedAt`, `isLoading`, `isTransitioning`, `statusCode`, and `redirect` stores have been removed, along with the pending/cache stores and their setters. `StoreConfig.init` has also been removed. Read application-facing state from `router.state`; preload and cache coordination are now internal. - Removed `RouterCore` members `getMatch()`, `updateMatch()`, `cancelMatch()`, and `cancelMatches()` — read matches from `router.state.matches` (e.g. `router.state.matches.find((m) => m.id === id)`); there is no replacement for mutating or cancelling an individual in-flight match from outside the router. -- Removed `hasNotFoundMatch()` — use `router.state.matches.some((m) => m.status === 'notFound')`. -- Removed `looseRoutesById` — use `routesById`. -- Removed `isPrerendering()`, `isViewTransitionTypesSupported`, and `viewTransitionPromise`, with no replacement. -- Removed `getParsedLocationHref()` and `clearExpiredCache()`, with no replacement — expired cache entries are now reconciled automatically as part of match commit. -- Removed `latestLoadPromise` and `beforeLoad()`, with no replacement. -- `commitLocationPromise` and `pendingBuiltLocation` are now private (`_commitPromise`, `_pendingLocation`) and no longer part of the public `RouterCore` surface. +- Removed `RouterCore.hasNotFoundMatch()` — use `router.state.matches.some((m) => m.status === 'notFound')`. +- Removed `RouterCore.looseRoutesById` — use `routesById`. +- Removed `RouterCore.isPrerendering()`, `RouterCore.isViewTransitionTypesSupported`, and `RouterCore.viewTransitionPromise`, with no replacement. +- Removed `RouterCore.getParsedLocationHref()` and `RouterCore.clearExpiredCache()`, with no replacement — expired cache entries are now reconciled automatically as part of match commit. +- Removed `RouterCore.latestLoadPromise` and `RouterCore.beforeLoad()`, with no replacement. +- `RouterCore.commitLocationPromise` and `RouterCore.pendingBuiltLocation` have been replaced by the internal `_commitPromise` and `_pendingLocation` fields. - Removed the exported `GetMatchFn` and `UpdateMatchFn` types, along with the methods they typed. - Removed the standalone `getMatchedRoutes()` export from `@tanstack/router-core` — use the `router.getMatchedRoutes()` instance method instead. +- `RouterCore.loadRouteChunk()` no longer accepts an array of component types as its second argument. One-argument usage is unchanged; the optional second argument is now `'errorComponent'`, `'notFoundComponent'`, or `false` for internal boundary loading. +- Removed `Redirect.redirectHandled`, which was internal redirect bookkeeping. - `MatchRoutesOpts.preload` and `MatchRoutesOpts.dest` have been removed. -- `StartTransitionFn` is now `(fn, expected, urgent?) => Promise` (previously `(fn) => void`). This only affects custom framework adapters that implement `startTransition`. +- `StartTransitionFn` is now `(fn, expected) => Promise` (previously `(fn) => void`). This only affects custom framework adapters that implement `startTransition`. diff --git a/packages/router-devtools-core/src/AgeTicker.tsx b/packages/router-devtools-core/src/AgeTicker.tsx index e3bd6573581..5a615be54ff 100644 --- a/packages/router-devtools-core/src/AgeTicker.tsx +++ b/packages/router-devtools-core/src/AgeTicker.tsx @@ -47,7 +47,7 @@ export function AgeTicker({ const staleTime = route.options.staleTime ?? router().options.defaultStaleTime ?? 0 const gcTime = - route.options.gcTime ?? router().options.defaultGcTime ?? 30 * 60 * 1000 + route.options.gcTime ?? router().options.defaultGcTime ?? 300_000 return (
staleTime))}> diff --git a/packages/router-devtools-core/tests/cache-replacement.test.ts b/packages/router-devtools-core/tests/cache-replacement.test.ts index 1da523a1fb4..e44e1cce533 100644 --- a/packages/router-devtools-core/tests/cache-replacement.test.ts +++ b/packages/router-devtools-core/tests/cache-replacement.test.ts @@ -35,7 +35,7 @@ describe('cached matches', () => { vi.useRealTimers() }) - it('refreshes when an existing cache entry is replaced', async () => { + it('uses the default gc time and refreshes replaced cache entries', async () => { vi.useFakeTimers() const route = { @@ -88,6 +88,7 @@ describe('cached matches', () => { ), ).not.toBeNull() }) + expect(container.textContent).toContain('5min') const cachedMatch = container.querySelector( '[aria-label="Open match details for cached-match"]',