Skip to content

perf(react-router): cache link pathname interpolation - #8249

Draft
Sheraff wants to merge 1 commit into
mainfrom
codex/cache-link-interpolation
Draft

perf(react-router): cache link pathname interpolation#8249
Sheraff wants to merge 1 commit into
mainfrom
codex/cache-link-interpolation

Conversation

@Sheraff

@Sheraff Sheraff commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

🎯 Changes

Supersedes #8229.

Mounted React links rebuild their location on navigation even when their pathname dependencies have not changed. Cache the interpolated, decoded pathname in a slot owned by each mounted link. Continue resolving params and destination routes and building the rest of the location on every call, so callbacks, search serialization, URL rewrites, masks, hash and state remain fresh.

  • Key reuse by the resolved route template, decoder identity and consumed parameter values, after parameter updaters and route stringifiers have run. Relative and inherited destinations can hit when their effective dependencies stay unchanged.
  • Track absent optional parameters as dependencies, so an omitted segment becoming present invalidates the cache. Track _splat without creating and deleting its legacy * alias; ordinary interpolation still returns that alias.
  • Compare resolved values directly, following the documented stringifier contract of Record<string, string>.
  • Keep the slot across link option updates. Route branches and middleware are resolved afresh, so pathname reuse does not require caching or invalidating route-tree state.

React supplies the cache slot. Solid and Vue bindings are unchanged; they still incur the shared core bundle addition and need separate reactive integration and performance validation before using it.

Results

Production bundle comparison against c18e690814, across all 18 scenarios: +145 to +193 gzip bytes, with no additional JavaScript files.

Scenario Added gzip bytes
React Router minimal +166
React Router full +184
Solid Router minimal +153
Vue Router minimal +165

In the final focused core run, a warm literal cache took 0.15999 ms per 100 builds, versus 0.24971 ms without the slot: 35.9% less time in that fixture.

The final simplification was also measured against the preceding implementation with descriptor checks and cold-entry validation:

Fixture Before ms/100 Final ms/100 Mean change
Uncached literal control 0.25482 0.24971 -2.0%
Warm cached literal 0.16498 0.15999 -3.0%
Cold literal slots 0.27188 0.25796 -5.1%

Final RME was ±0.10–0.16%, with 3,877–6,251 samples. The uncached control also moved, so the small warm difference should be interpreted cautiously. This measures the combined simplification, not the cost of delete alone.

For broader context, the production React navigation fixture (202 persistent links, eight navigations per sample) measured 5.3902 → 3.8739 ms, or 28.1% less time, on the preceding version before the final descriptor/alias simplification. RME was ±0.64% / ±0.59%, with 1,856 / 2,582 samples. That navigation benchmark was not rerun on this exact revision. These are local workload measurements; cold calls and changing dependencies do not receive the same benefit as warm hits.

Validation

Production builds, all 18 bundle scenarios and benchmark fixture comparisons/timings were run. Coverage was added for cache hits and invalidation, optional/inherited/splat params, callbacks, rewrites, masks, HMR middleware changes and React link updates. Unit, type and e2e suites have not been rerun for this latest revision.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested code changes locally with the relevant test commands, or tests do not apply to this pull request.
  • I fully understand the code in this pull request, including any code generated with AI assistance.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nx-cloud

nx-cloud Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 4178075

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ❌ Failed 11m 35s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 2m 10s View ↗

☁️ Nx Cloud last updated this comment at 2026-09-05 15:08:33 UTC

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

5 package(s) bumped directly, 18 bumped as dependents.

🟩 Patch bumps

Package Version Reason
@tanstack/react-router 1.170.32 → 1.170.33 Changeset
@tanstack/router-core 1.171.27 → 1.171.28 Changeset
@tanstack/solid-router 1.170.30 → 1.170.31 Changeset
@tanstack/start-plugin-core 1.171.39 → 1.171.40 Changeset
@tanstack/vue-router 1.170.29 → 1.170.30 Changeset
@tanstack/react-start 1.168.49 → 1.168.50 Dependent
@tanstack/react-start-client 1.168.30 → 1.168.31 Dependent
@tanstack/react-start-rsc 0.1.48 → 0.1.49 Dependent
@tanstack/react-start-server 1.167.37 → 1.167.38 Dependent
@tanstack/router-cli 1.167.33 → 1.167.34 Dependent
@tanstack/router-generator 1.167.33 → 1.167.34 Dependent
@tanstack/router-plugin 1.168.35 → 1.168.36 Dependent
@tanstack/router-vite-plugin 1.167.35 → 1.167.36 Dependent
@tanstack/solid-start 1.168.47 → 1.168.48 Dependent
@tanstack/solid-start-client 1.168.29 → 1.168.30 Dependent
@tanstack/solid-start-server 1.167.36 → 1.167.37 Dependent
@tanstack/start-client-core 1.170.27 → 1.170.28 Dependent
@tanstack/start-server-core 1.169.31 → 1.169.32 Dependent
@tanstack/start-static-server-functions 1.167.32 → 1.167.33 Dependent
@tanstack/start-storage-context 1.167.29 → 1.167.30 Dependent
@tanstack/vue-start 1.168.46 → 1.168.47 Dependent
@tanstack/vue-start-client 1.167.32 → 1.167.33 Dependent
@tanstack/vue-start-server 1.167.36 → 1.167.37 Dependent

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Benchmarks

  • Commit: f3a6ac951b61
  • Measured at: 2026-09-05T14:57:15.230Z
  • Baseline source: history:28a5e4504e4e
  • Dashboard: bundle-size history

The following scenarios have bundle-size changes compared with the baseline:

Scenario Current (gzip) Initial (gzip) Raw Brotli Trend
react-router.minimal 83.9 KiB
+170 B
83.8 KiB
+169 B
262.5 KiB
+434 B
73.1 KiB
+178 B
▃▂▂▂▂▁▂▂▂▂▂█
react-router.full 87.5 KiB
+190 B
87.3 KiB
+186 B
274.2 KiB
+424 B
76.2 KiB
+299 B
▃▂▂▂▂▁▂▂▂▂▂█
solid-router.minimal 33.3 KiB
+153 B
33.2 KiB
+154 B
96.5 KiB
+388 B
30.0 KiB
+116 B
▃▂▂▂▂▁▂▂▂▃▃█
solid-router.full 38.1 KiB
+173 B
38.0 KiB
+168 B
111.1 KiB
+388 B
34.4 KiB
+210 B
▃▂▂▂▂▁▂▂▂▂▂█
vue-router.minimal 49.6 KiB
+172 B
49.5 KiB
+173 B
138.5 KiB
+391 B
44.8 KiB
+182 B
▃▃▃▃▃▁▂▂▂▂▂█
vue-router.full 55.2 KiB
+152 B
55.1 KiB
+153 B
156.7 KiB
+388 B
49.7 KiB
+116 B
▃▃▃▃▃▁▂▂▂▂▂█
react-start.minimal 96.9 KiB
+180 B
96.7 KiB
+178 B
304.7 KiB
+442 B
83.9 KiB
+178 B
▃▂▂▂▂▁▂▂▂▂▂█
react-start.query-integration 104.2 KiB
+195 B
104.1 KiB
+194 B
331.2 KiB
+442 B
90.3 KiB
+103 B
▃▂▂▂▂▁▂▂▂▂▂█
react-start.deferred-hydration 97.6 KiB
+183 B
96.7 KiB
+179 B
306.1 KiB
+442 B
84.6 KiB
+40 B
▃▂▂▂▂▁▁▂▂▂▂█
react-start.full 100.0 KiB
+186 B
99.9 KiB
+189 B
314.5 KiB
+417 B
86.7 KiB
+228 B
▃▂▂▂▂▁▂▂▂▂▂█
react-start.rsbuild.minimal 100.1 KiB
+163 B
99.9 KiB
+163 B
315.1 KiB
+455 B
86.4 KiB
+74 B
▄▃▃▃▃▁▂▂▂▁▁█
react-start.rsbuild.minimal-iife 100.5 KiB
+167 B
100.4 KiB
+167 B
316.0 KiB
+455 B
86.7 KiB
+30 B
▄▃▃▃▃▁▂▁▁▁▂█
react-start.rsbuild.full 103.4 KiB
+165 B
103.3 KiB
+165 B
325.2 KiB
+455 B
89.2 KiB
+180 B
▄▃▃▃▃▁▁▁▁▁▁█
solid-start.minimal 46.1 KiB
+150 B
46.0 KiB
+150 B
137.6 KiB
+388 B
41.0 KiB
+104 B
▃▂▂▂▂▁▂▂▂▂▂█
solid-start.deferred-hydration 49.2 KiB
+164 B
46.1 KiB
+162 B
145.1 KiB
+388 B
43.8 KiB
+125 B
▂▂▂▂▂▁▂▂▂▂▂█
solid-start.full 51.2 KiB
+160 B
51.1 KiB
+160 B
153.0 KiB
+388 B
45.4 KiB
+163 B
▃▂▂▂▂▁▂▂▂▂▂█
vue-start.minimal 65.8 KiB
+149 B
65.6 KiB
+148 B
189.4 KiB
+388 B
58.4 KiB
+80 B
▃▃▃▃▃▁▂▂▂▂▂█
vue-start.full 69.6 KiB
+144 B
69.4 KiB
+145 B
201.7 KiB
+382 B
61.8 KiB
+124 B
▄▃▃▃▃▁▂▂▂▂▂█

Current gzip tracks all emitted client JS chunks. Initial gzip tracks only the entry/import graph. Trend sparkline is historical current gzip ending with this PR measurement; lower is better.

@pkg-pr-new

pkg-pr-new Bot commented Sep 5, 2026

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@8249

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@8249

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@8249

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@8249

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@8249

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@8249

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@8249

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@8249

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@8249

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@8249

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@8249

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@8249

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@8249

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@8249

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@8249

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@8249

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@8249

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@8249

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@8249

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@8249

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@8249

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@8249

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@8249

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@8249

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@8249

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@8249

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@8249

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@8249

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@8249

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@8249

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@8249

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@8249

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@8249

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@8249

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@8249

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@8249

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@8249

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@8249

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@8249

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@8249

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@8249

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@8249

commit: 4178075

@codspeed-hq

codspeed-hq Bot commented Sep 5, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 9.91%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 9 improved benchmarks
❌ 8 regressed benchmarks
✅ 163 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory mem server error-paths not-found (vue) 485.2 KB 2,289.5 KB -78.81%
Memory mem server error-paths not-found (solid) 577.2 KB 917.7 KB -37.11%
Memory mem client navigation-churn (solid) 612.8 KB 724.7 KB -15.43%
Memory mem client unique-location-churn (vue) 420.2 KB 456.6 KB -7.97%
Memory mem server aborted-requests (react) 798.2 KB 848.2 KB -5.9%
Memory mem server server-fn-churn (react) 371.7 KB 391.6 KB -5.08%
Memory mem server peak-large-page (solid) 1.1 MB 1.1 MB -4.2%
Memory mem client mount-unmount (solid) 513.7 KB 533.7 KB -3.75%
Simulation client-links navigation loop (react) 149 ms 123.3 ms +20.82%
Simulation client-nested-params navigation loop (react) 149.5 ms 130.8 ms +14.3%
Simulation ssr server-fn POST (solid) 159.6 ms 147.9 ms +7.89%
Simulation ssr server-fn not-found (solid) 154 ms 145.2 ms +6.07%
Simulation ssr server-fn GET (solid) 180 ms 170.8 ms +5.44%
Simulation ssr server-fn send-context (solid) 154.7 ms 147 ms +5.2%
Memory mem server error-paths redirect (react) 317.8 KB 302.4 KB +5.07%
Simulation ssr server-fn redirect (solid) 122.1 ms 117.5 ms +3.87%
Simulation client-route-tree-scale navigation loop (react) 84.4 ms 81.4 ms +3.78%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing codex/cache-link-interpolation (4178075) with main (28a5e45)

Open in CodSpeed

@nx-cloud nx-cloud Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nx Cloud is proposing a fix for your failed CI:

We corrected the search middleware in the HMR test to use the proper composition pattern: calling next(search) first and merging { rebuilt: true } onto the result, rather than passing the modified search into next. This fixes the failure because applySearchMiddleware's terminal case always returns {} when no dest.search is set, so any modifications passed into next were silently discarded. With the corrected pattern the middleware appends its key on top of the resolved base, matching the established contract used by retainSearchParams.

Tip

We verified this fix by re-running @tanstack/router-core:test:unit.

diff --git a/packages/router-core/tests/build-location-cache.test.ts b/packages/router-core/tests/build-location-cache.test.ts
index 4639c097..6d2b2888 100644
--- a/packages/router-core/tests/build-location-cache.test.ts
+++ b/packages/router-core/tests/build-location-cache.test.ts
@@ -491,7 +491,7 @@ describe('buildLocation memo (_buildCache)', () => {
     // `handleRouteUpdate` in router-plugin swaps route options in place and
     // rebuilds the tree without touching `router.options`.
     router.routesByPath['/items/$id']!.options.search = {
-      middlewares: [({ search, next }) => next({ ...search, rebuilt: true })],
+      middlewares: [({ search, next }) => ({ ...next(search), rebuilt: true })],
     }
     router.setRoutes(router.buildRouteTree())
     const rebuilt = buildBoth(router, cache, opts)

Apply fix via Nx Cloud  Reject fix via Nx Cloud


Or Apply changes locally with:

npx nx-cloud apply-locally bpnw-bITT

Apply fix locally with your editor ↗   View interactive diff ↗



🎓 Learn more about Self-Healing CI on nx.dev

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant