Skip to content

Commit ad084d4

Browse files
feat: cache routeTree on the server in-between requests (#6475)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent e190d8d commit ad084d4

File tree

2 files changed

+61
-14
lines changed

2 files changed

+61
-14
lines changed

packages/router-core/src/new-process-route-tree.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,17 @@ export function trimPathRight(path: string) {
778778
return path === '/' ? path : path.replace(/\/{1,}$/, '')
779779
}
780780

781+
export interface ProcessRouteTreeResult<
782+
TRouteLike extends Extract<RouteLike, { fullPath: string }> & { id: string },
783+
> {
784+
/** Should be considered a black box, needs to be provided to all matching functions in this module. */
785+
processedTree: ProcessedTree<TRouteLike, any, any>
786+
/** A lookup map of routes by their unique IDs. */
787+
routesById: Record<string, TRouteLike>
788+
/** A lookup map of routes by their trimmed full paths. */
789+
routesByPath: Record<string, TRouteLike>
790+
}
791+
781792
/**
782793
* Processes a route tree into a segment trie for efficient path matching.
783794
* Also builds lookup maps for routes by ID and by trimmed full path.
@@ -791,14 +802,7 @@ export function processRouteTree<
791802
caseSensitive: boolean = false,
792803
/** Optional callback invoked for each route during processing. */
793804
initRoute?: (route: TRouteLike, index: number) => void,
794-
): {
795-
/** Should be considered a black box, needs to be provided to all matching functions in this module. */
796-
processedTree: ProcessedTree<TRouteLike, any, any>
797-
/** A lookup map of routes by their unique IDs. */
798-
routesById: Record<string, TRouteLike>
799-
/** A lookup map of routes by their trimmed full paths. */
800-
routesByPath: Record<string, TRouteLike>
801-
} {
805+
): ProcessRouteTreeResult<TRouteLike> {
802806
const segmentTree = createStaticNode<TRouteLike>(routeTree.fullPath)
803807
const data = new Uint16Array(6)
804808
const routesById = {} as Record<string, TRouteLike>

packages/router-core/src/router.ts

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,11 @@ import {
3838
executeRewriteOutput,
3939
rewriteBasepath,
4040
} from './rewrite'
41-
import type { ProcessedTree } from './new-process-route-tree'
41+
import type { LRUCache } from './lru-cache'
42+
import type {
43+
ProcessRouteTreeResult,
44+
ProcessedTree,
45+
} from './new-process-route-tree'
4246
import type { SearchParser, SearchSerializer } from './searchParams'
4347
import type { AnyRedirect, ResolvedRedirect } from './redirect'
4448
import type {
@@ -872,6 +876,17 @@ export type CreateRouterFn = <
872876
TDehydrated
873877
>
874878

879+
declare global {
880+
// eslint-disable-next-line no-var
881+
var __TSR_CACHE__:
882+
| {
883+
routeTree: AnyRoute
884+
processRouteTreeResult: ProcessRouteTreeResult<AnyRoute>
885+
resolvePathCache: LRUCache<string, string>
886+
}
887+
| undefined
888+
}
889+
875890
/**
876891
* Core, framework-agnostic router engine that powers TanStack Router.
877892
*
@@ -922,6 +937,7 @@ export class RouterCore<
922937
routesById!: RoutesById<TRouteTree>
923938
routesByPath!: RoutesByPath<TRouteTree>
924939
processedTree!: ProcessedTree<TRouteTree, any, any>
940+
resolvePathCache!: LRUCache<string, string>
925941
isServer!: boolean
926942
pathParamsDecoder?: (encoded: string) => string
927943

@@ -1026,7 +1042,28 @@ export class RouterCore<
10261042

10271043
if (this.options.routeTree !== this.routeTree) {
10281044
this.routeTree = this.options.routeTree as TRouteTree
1029-
this.buildRouteTree()
1045+
let processRouteTreeResult: ProcessRouteTreeResult<TRouteTree>
1046+
if (
1047+
this.isServer &&
1048+
globalThis.__TSR_CACHE__ &&
1049+
globalThis.__TSR_CACHE__.routeTree === this.routeTree
1050+
) {
1051+
const cached = globalThis.__TSR_CACHE__
1052+
this.resolvePathCache = cached.resolvePathCache
1053+
processRouteTreeResult = cached.processRouteTreeResult as any
1054+
} else {
1055+
this.resolvePathCache = createLRUCache(1000)
1056+
processRouteTreeResult = this.buildRouteTree()
1057+
// only cache if nothing else is cached yet
1058+
if (this.isServer && globalThis.__TSR_CACHE__ === undefined) {
1059+
globalThis.__TSR_CACHE__ = {
1060+
routeTree: this.routeTree,
1061+
processRouteTreeResult: processRouteTreeResult as any,
1062+
resolvePathCache: this.resolvePathCache,
1063+
}
1064+
}
1065+
}
1066+
this.setRoutes(processRouteTreeResult)
10301067
}
10311068

10321069
if (!this.__store && this.latestLocation) {
@@ -1109,7 +1146,7 @@ export class RouterCore<
11091146
}
11101147

11111148
buildRouteTree = () => {
1112-
const { routesById, routesByPath, processedTree } = processRouteTree(
1149+
const result = processRouteTree(
11131150
this.routeTree,
11141151
this.options.caseSensitive,
11151152
(route, i) => {
@@ -1119,9 +1156,17 @@ export class RouterCore<
11191156
},
11201157
)
11211158
if (this.options.routeMasks) {
1122-
processRouteMasks(this.options.routeMasks, processedTree)
1159+
processRouteMasks(this.options.routeMasks, result.processedTree)
11231160
}
11241161

1162+
return result
1163+
}
1164+
1165+
setRoutes({
1166+
routesById,
1167+
routesByPath,
1168+
processedTree,
1169+
}: ProcessRouteTreeResult<TRouteTree>) {
11251170
this.routesById = routesById as RoutesById<TRouteTree>
11261171
this.routesByPath = routesByPath as RoutesByPath<TRouteTree>
11271172
this.processedTree = processedTree
@@ -1221,8 +1266,6 @@ export class RouterCore<
12211266
return location
12221267
}
12231268

1224-
resolvePathCache = createLRUCache<string, string>(1000)
1225-
12261269
/** Resolve a path against the router basepath and trailing-slash policy. */
12271270
resolvePathWithBase = (from: string, path: string) => {
12281271
const resolvedPath = resolvePath({

0 commit comments

Comments
 (0)