From 430a389c9f7e1d7a71e86494347ce2c63880a3cf Mon Sep 17 00:00:00 2001 From: Sheraff Date: Thu, 11 Jun 2026 14:31:07 +0200 Subject: [PATCH] perf(router-core): cache lightweight route matches --- packages/router-core/src/router.ts | 36 +++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts index 9ff7b82b05..2197dab737 100644 --- a/packages/router-core/src/router.ts +++ b/packages/router-core/src/router.ts @@ -909,6 +909,18 @@ export const locationHistoryActions = new WeakMap< HistoryAction >() +type LightweightRouteMatchResult = { + matchedRoutes: ReadonlyArray + fullPath: string + search: Record + params: Record +} + +type LightweightRouteMatchCacheEntry = [ + lastMatchId: string | undefined, + result: LightweightRouteMatchResult, +] + export type CreateRouterFn = < TRouteTree extends AnyRoute, TTrailingSlashOption extends TrailingSlashOption = 'never', @@ -1002,6 +1014,10 @@ export class RouterCore< processedTree!: ProcessedTree resolvePathCache!: LRUCache private routeBranchCache = new WeakMap>() + private lightweightCache = new WeakMap< + ParsedLocation, + LightweightRouteMatchCacheEntry + >() isServer!: boolean pathParamsDecoder?: (encoded: string) => string protocolAllowlist!: Set @@ -1719,12 +1735,15 @@ export class RouterCore< * Only computes fullPath, accumulated search, and params - skipping expensive * operations like AbortController, ControlledPromise, loaderDeps, and full match objects. */ - private matchRoutesLightweight(location: ParsedLocation): { - matchedRoutes: ReadonlyArray - fullPath: string - search: Record - params: Record - } { + private matchRoutesLightweight( + location: ParsedLocation, + ): LightweightRouteMatchResult { + const lastStateMatchId = last(this.stores.matchesId.get()) + const cached = this.lightweightCache.get(location) + if (cached && cached[0] === lastStateMatchId) { + return cached[1] + } + const { matchedRoutes, routeParams } = this.getMatchedRoutes( location.pathname, ) @@ -1753,7 +1772,6 @@ export class RouterCore< } // Determine params: reuse from state if possible, otherwise parse - const lastStateMatchId = last(this.stores.matchesId.get()) const lastStateMatch = lastStateMatchId && this.stores.matchStores.get(lastStateMatchId)?.get() const canReuseParams = @@ -1780,12 +1798,14 @@ export class RouterCore< params = strictParams } - return { + const result = { matchedRoutes, fullPath: lastRoute.fullPath, search: accumulatedSearch, params, } + this.lightweightCache.set(location, [lastStateMatchId, result]) + return result } cancelMatch = (id: string) => {