From 65b6654a279b1e804993295f3352095686d64026 Mon Sep 17 00:00:00 2001 From: Manuel Schiller <6340397+schiller-manuel@users.noreply.github.com> Date: Sat, 12 Sep 2026 01:45:46 +0200 Subject: [PATCH] perf(router-core): keep search middleware collection optimized CodSpeed's memory instrument flagged the interrupted-navigations client benchmark on the location-reuse changes (react +14.6%, vue +29.8%, solid +10.8% peak memory). Under its worker flags (`--no-opt --predictable`, single threaded) that benchmark's peak is set by a synchronous Maglev compile that happens inside the measured run. `getSearchMiddlewares` is compiled during the preparation calls and then deoptimized on the first property load off a route's options. Before, its only caller was the small `applySearchMiddleware`, which Maglev compiled next and inlined it into, so the callee never tiered up again. Since `build` calls it directly and does not inline it, it became hot again during the measured run, and its recompilation (about four 32 KB zone segments) set the peak. Iterating `destRoutes` with a counted loop instead of `for...of` keeps the function optimized once compiled: no deoptimization and no recompilation in the measured window. Middleware order and live route-option reads are unchanged. Hoisting the option reads alone did not help. Local reproduction with a malloc interposer, CodSpeed's worker flags and seven preparation calls, macOS arm64 / Node 24.8.0 (deterministic peaks): - react: 248880 (base) -> 287760 (+15.6%) -> 248944 with this change - vue: 256704 -> 292656 (+14.0%) -> 256640 - solid: 287408 -> 288240 (+0.3%) -> 287632 These are local native allocations, not a new Linux CodSpeed CI run. Bundle (react-router.minimal gzip): +8. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .changeset/steady-loops-compile.md | 5 +++++ packages/router-core/src/router.ts | 7 +++++-- 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 .changeset/steady-loops-compile.md diff --git a/.changeset/steady-loops-compile.md b/.changeset/steady-loops-compile.md new file mode 100644 index 0000000000..5a243a4205 --- /dev/null +++ b/.changeset/steady-loops-compile.md @@ -0,0 +1,5 @@ +--- +'@tanstack/router-core': patch +--- + +Collect search middlewares with a counted loop so the optimized code stays valid across navigations. This removes a JIT recompilation that raised peak memory in the interrupted-navigations client memory benchmark. diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts index 19ea2b8aed..b12e5df297 100644 --- a/packages/router-core/src/router.ts +++ b/packages/router-core/src/router.ts @@ -2900,14 +2900,17 @@ function needsInheritedParams( const EMPTY_RECORD: Record = Object.freeze({}) // Keep this separate from recursive execution to limit JIT compiler memory. +// A counted loop instead of `for...of`: Maglev's inlined array iteration +// deoptimizes on route option shapes and recompiles later, whereas indexed +// reads stay optimized once compiled. function getSearchMiddlewares( destRoutes: ReadonlyArray, includeValidateSearch: boolean | undefined, ) { const middlewares = [] as Array> - for (const route of destRoutes) { - const routeOptions = route.options + for (let i = 0; i < destRoutes.length; i++) { + const routeOptions = destRoutes[i]!.options if ('search' in routeOptions) { if (routeOptions.search?.middlewares) { middlewares.push(...routeOptions.search.middlewares)