-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat: cache routeTree on the server in-between requests #6475
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
📝 WalkthroughWalkthroughReworks route-tree processing to return a structured Changes
Sequence DiagramsequenceDiagram
participant RC as RouterCore
participant GC as __TSR_CACHE__ (Global)
participant BRT as buildRouteTree()
participant SR as setRoutes()
RC->>RC: routeTree changed
alt Server & cache hit
RC->>GC: check cached processRouteTreeResult + resolvePathCache
GC-->>RC: return cached result
RC->>SR: setRoutes(cached result)
else Cache miss / client
RC->>BRT: create LRU cache → call buildRouteTree()
BRT-->>RC: return ProcessRouteTreeResult
RC->>GC: populate __TSR_CACHE__ (server) if applicable
RC->>SR: setRoutes(new result)
end
SR->>RC: initialize processedTree, routesById, routesByPath
Estimated code review effort🎯 4 (Complex) | ⏱️ ~35 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx affected --targets=test:eslint,test:unit,tes... |
❌ Failed | 9m 50s | View ↗ |
nx run-many --target=build --exclude=examples/*... |
✅ Succeeded | 1m 43s | View ↗ |
☁️ Nx Cloud last updated this comment at 2026-01-23 23:33:33 UTC
| @@ -1120,9 +1155,17 @@ export class RouterCore< | |||
| }, | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it OK that this won't be called again when reusing the cached process tree?
It seems to be doing a bunch of important things
this.parentRoute = this.options.getParentRoute?.()
this._path = path as TPath
this._id = id as TId
this._fullPath = fullPath as TFullPath
this._to = trimPathRight(fullPath) as TrimPathRight<TFullPath>There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this sets path, id, to, fullPath of a route. those are static
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh right, and they are on the routeTree itself, which is what survives across requests anyway, regardless of this PR.
Ok so it is fine then
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
packages/router-core/src/new-process-route-tree.ts (1)
808-825: Avoid prototype-collision bugs inroutesById/routesByPath.
Using{}plusincan mis-detect duplicates for ids liketoStringand allows__proto__keys. PreferObject.create(null)+hasOwnProperty(or aMap).Suggested fix
- const routesById = {} as Record<string, TRouteLike> - const routesByPath = {} as Record<string, TRouteLike> + const routesById = Object.create(null) as Record<string, TRouteLike> + const routesByPath = Object.create(null) as Record<string, TRouteLike> @@ - invariant( - !(route.id in routesById), + invariant( + !Object.prototype.hasOwnProperty.call(routesById, route.id), `Duplicate routes found with id: ${String(route.id)}`, )packages/router-core/src/router.ts (2)
28-44: Fix lint: type-only import & member ordering.
ESLint flagsLRUCacheas type-only and the member ordering in the type import.Suggested fix
-import { createLRUCache, LRUCache } from './lru-cache' +import { createLRUCache } from './lru-cache' +import type { LRUCache } from './lru-cache' @@ -import type { - ProcessedTree, - ProcessRouteTreeResult, -} from './new-process-route-tree' +import type { + ProcessRouteTreeResult, + ProcessedTree, +} from './new-process-route-tree'
1164-1181: Avoid mutating shared cached maps when adding notFoundRoute.
When reusing the global cache,routesById/routesByPathare shared. Mutating them here can leaknotFoundRouteacross router instances. Clone before mutation or keepnotFoundRouteseparately.Suggested fix
- this.routesById = routesById as RoutesById<TRouteTree> - this.routesByPath = routesByPath as RoutesByPath<TRouteTree> + const routesByIdLocal = { ...routesById } + const routesByPathLocal = { ...routesByPath } + this.routesById = routesByIdLocal as RoutesById<TRouteTree> + this.routesByPath = routesByPathLocal as RoutesByPath<TRouteTree>
🤖 Fix all issues with AI agents
In `@packages/router-core/src/router.ts`:
- Around line 879-887: The global declaration for __TSR_CACHE__ triggers the
no-var lint; either replace the leading var with let in the declare global block
(changing "var __TSR_CACHE__" to "let __TSR_CACHE__") or, if using var is
necessary for global augmentation semantics, suppress the rule locally by adding
an eslint disable comment (e.g., // eslint-disable-next-line no-var) immediately
above the declaration; update the declaration around the __TSR_CACHE__ symbol
(and its properties routeTree, processRouteTreeResult, resolvePathCache and
related types ProcessRouteTreeResult, LRUCache, AnyRoute) accordingly so the
linter no-var complaint is resolved.
- Around line 1044-1066: The current server-side cache
(globalThis.__TSR_CACHE__) reuses processRouteTreeResult for the same
this.routeTree without accounting for options like caseSensitive and routeMasks;
update the cache key/shape or assert options match before reuse: when
checking/setting globalThis.__TSR_CACHE__ in the constructor logic around
processRouteTreeResult and resolvePathCache, include the route-matching options
(e.g., this.caseSensitive and this.routeMasks) in the cached object and in the
equality check (or throw/assert if they differ) so that this.buildRouteTree()
and this.setRoutes(processRouteTreeResult) never reuse a result produced under
different option values.
| let processRouteTreeResult: ProcessRouteTreeResult<TRouteTree> | ||
| if ( | ||
| this.isServer && | ||
| globalThis.__TSR_CACHE__ && | ||
| globalThis.__TSR_CACHE__.routeTree === this.routeTree | ||
| ) { | ||
| const cached = globalThis.__TSR_CACHE__ | ||
| this.resolvePathCache = cached.resolvePathCache | ||
| processRouteTreeResult = cached.processRouteTreeResult as any | ||
| } else { | ||
| this.resolvePathCache = createLRUCache(1000) | ||
| processRouteTreeResult = this.buildRouteTree() | ||
| // only cache if nothing else is cached yet | ||
| if (this.isServer && globalThis.__TSR_CACHE__ === undefined) { | ||
| globalThis.__TSR_CACHE__ = { | ||
| routeTree: this.routeTree, | ||
| processRouteTreeResult: processRouteTreeResult as any, | ||
| resolvePathCache: this.resolvePathCache, | ||
| } | ||
| } | ||
| } | ||
| this.setRoutes(processRouteTreeResult) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cache reuse should be gated by option-sensitive keys.
processRouteTree/masks depend on caseSensitive and routeMasks. Reusing cached results for the same routeTree but different options can yield incorrect matching. Consider including these options in the cache key (and cache shape) or asserting they match before reuse.
Possible direction
- if (
- this.isServer &&
- globalThis.__TSR_CACHE__ &&
- globalThis.__TSR_CACHE__.routeTree === this.routeTree
- ) {
+ if (
+ this.isServer &&
+ globalThis.__TSR_CACHE__ &&
+ globalThis.__TSR_CACHE__.routeTree === this.routeTree &&
+ globalThis.__TSR_CACHE__.caseSensitive === this.options.caseSensitive &&
+ globalThis.__TSR_CACHE__.routeMasks === this.options.routeMasks
+ ) {
@@
- globalThis.__TSR_CACHE__ = {
- routeTree: this.routeTree,
+ globalThis.__TSR_CACHE__ = {
+ routeTree: this.routeTree,
+ caseSensitive: this.options.caseSensitive,
+ routeMasks: this.options.routeMasks,
processRouteTreeResult: processRouteTreeResult as any,
resolvePathCache: this.resolvePathCache,
}🤖 Prompt for AI Agents
In `@packages/router-core/src/router.ts` around lines 1044 - 1066, The current
server-side cache (globalThis.__TSR_CACHE__) reuses processRouteTreeResult for
the same this.routeTree without accounting for options like caseSensitive and
routeMasks; update the cache key/shape or assert options match before reuse:
when checking/setting globalThis.__TSR_CACHE__ in the constructor logic around
processRouteTreeResult and resolvePathCache, include the route-matching options
(e.g., this.caseSensitive and this.routeMasks) in the cached object and in the
equality check (or throw/assert if they differ) so that this.buildRouteTree()
and this.setRoutes(processRouteTreeResult) never reuse a result produced under
different option values.

Summary by CodeRabbit
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.