Skip to content

perf(router-core): cache current-location lightweight matches#7011

Open
Sheraff wants to merge 2 commits intomainfrom
flo/current-location-lightweight-cache
Open

perf(router-core): cache current-location lightweight matches#7011
Sheraff wants to merge 2 commits intomainfrom
flo/current-location-lightweight-cache

Conversation

@Sheraff
Copy link
Contributor

@Sheraff Sheraff commented Mar 22, 2026

Summary

  • cache matchRoutesLightweight when buildLocation is called with router.stores.location.state
  • keep the cache to a single entry keyed by the current location object so it invalidates when the store location changes
  • read fromSearch from currentLocation directly and drop the unused lightweight search accumulation

Testing

  • CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:unit --outputStyle=stream --skipRemoteCache -- tests/build-location.test.ts
  • CI=1 NX_DAEMON=false pnpm nx run @tanstack/router-core:test:types --outputStyle=stream --skipRemoteCache
  • CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav:test:perf --outputStyle=stream --skipRemoteCache

Summary by CodeRabbit

  • Refactor
    • Improved routing performance with a smarter lightweight route-matching and caching strategy.
    • Prevented stale query/search data by ensuring returned navigation data is copied, avoiding unintended mutations.
    • Cache now flushes on router updates to ensure up-to-date navigation results.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 22, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8ef8f06d-c439-4ec5-9a9b-cdd06d8573bc

📥 Commits

Reviewing files that changed from the base of the PR and between b745f7d and b845204.

📒 Files selected for processing (1)
  • packages/router-core/src/router.ts

📝 Walkthrough

Walkthrough

Introduces an internal lightweight-match cache keyed to location object identity, refactors matchRoutesLightweight to return a richer result with non-aliased search, makes buildLocation shallow-copy the search, and resets the lightweight cache on router update().

Changes

Cohort / File(s) Summary
Lightweight matching & cache
packages/router-core/src/router.ts
Added MatchRoutesLightweightResult/MatchRoutesLightweightCache types and a private matchRoutesLightweightCache; matchRoutesLightweight now caches by location object identity and only uses cache when this.stores.location.state === location; search assembly moved to sequential validation and returned search is shallow-copied in buildLocation; cache is cleared on update().

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through routes with nimble care,
I tucked match results in a hidden lair,
I copy the search so it won't cling,
And on each update I clear my thing,
A tiny cache to speed the spring.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: implementing a cache for lightweight route matches on the current location to improve performance in router-core.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch flo/current-location-lightweight-cache

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

@nx-cloud
Copy link

nx-cloud bot commented Mar 22, 2026

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit b845204

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

☁️ Nx Cloud last updated this comment at 2026-03-22 18:37:21 UTC

@github-actions
Copy link
Contributor

github-actions bot commented Mar 22, 2026

🚀 Changeset Version Preview

No changeset entries found. Merging this PR will not cause a version bump for any packages.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 22, 2026

Bundle Size Benchmarks

  • Commit: b9f9ea15b696
  • Measured at: 2026-03-22T18:25:58.597Z
  • Baseline source: history:b9f9ea15b696
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Raw Brotli Trend
react-router.minimal 88.21 KiB +58 B (+0.06%) 278.59 KiB 76.59 KiB ▁▁▁██████▅▅▅
react-router.full 91.36 KiB +50 B (+0.05%) 289.32 KiB 79.28 KiB ▁▁▁██████▅▅▆
solid-router.minimal 35.85 KiB +55 B (+0.15%) 108.49 KiB 32.23 KiB ███▃▃▃▃▃▃▁▁▁
solid-router.full 40.20 KiB +53 B (+0.13%) 121.63 KiB 36.02 KiB ███▃▃▃▃▃▃▁▁▁
vue-router.minimal 53.83 KiB +60 B (+0.11%) 154.72 KiB 48.27 KiB ▁▁▁██████▆▆▆
vue-router.full 58.61 KiB +58 B (+0.10%) 169.88 KiB 52.42 KiB ▁▁▁██████▆▆▆
react-start.minimal 102.52 KiB +84 B (+0.08%) 326.35 KiB 88.66 KiB ▁▁▁██████▄▄▅
react-start.full 105.91 KiB +55 B (+0.05%) 336.66 KiB 91.56 KiB ▁▁▁██████▅▅▅
solid-start.minimal 49.91 KiB +43 B (+0.08%) 154.68 KiB 44.01 KiB ███▃▃▃▃▃▃▁▁▁
solid-start.full 55.31 KiB +58 B (+0.10%) 170.51 KiB 48.61 KiB ███▄▄▄▄▄▄▁▁▁

Trend sparkline is historical gzip bytes ending with this PR measurement; lower is better.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 22, 2026

More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

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

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: b845204

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b745f7d39f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1844 to +1845
// From search should always use the current location
const fromSearch = lightweightResult.search
const fromSearch = currentLocation.search

Choose a reason for hiding this comment

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

P1 Badge Preserve validated search when using _fromLocation

When buildLocation is driven from an _fromLocation (render-time <Link>s do this via packages/react-router/src/link.tsx:407, and preloadRoute retries redirects with _fromLocation: next in router.ts:2863-2866), currentLocation.search is only the raw parsed query string, not the route-validated search state. Before this change matchRoutesLightweight re-ran validateSearch, so defaults like validateSearch: () => ({ postPage: 0 }) were still visible to search updaters even when the URL omitted ?postPage=. Now those updaters receive {} instead, which breaks relative links and redirect/preload flows that rely on inherited defaults (for example the redirect-preload case covered in packages/react-router/tests/link.test.tsx:3899-4020).

Useful? React with 👍 / 👎.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/router-core/src/router.ts (1)

1844-1849: ⚠️ Potential issue | 🟠 Major

Preserve a validated search snapshot here.

Line 1845 now feeds the live currentLocation.search into the middleware/updater chain. Because the validateSearch middlewares only run on unwind, search.middlewares and dest.search callbacks now observe raw parsed search instead of the validated/defaulted snapshot they used to receive. Any in-place mutation can also leak back into router state because this object is no longer copied like fromParams is. Please keep using a validated snapshot here (cached if you want the perf win) instead of the live location search.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router-core/src/router.ts` around lines 1844 - 1849, The code sets
fromSearch = currentLocation.search which feeds the live mutable search into
middleware/updater chain; instead capture and use the validated/defaulted search
snapshot used by validateSearch (not the live object) — e.g., build fromSearch
the same way fromParams is created (a shallow copy of the validated search
snapshot from lightweightResult or the cached validatedSearch), and ensure
search.middlewares and dest.search receive that copied snapshot so in-place
mutations cannot leak back into router state.
🧹 Nitpick comments (1)
packages/router-core/src/router.ts (1)

1690-1693: Refresh the lightweight-matching docstrings.

These comments still mention lightweight search accumulation, but the result now only carries matchedRoutes, fullPath, and params.

Also applies to: 1808-1810

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router-core/src/router.ts` around lines 1690 - 1693, Update the
docstrings that describe the lightweight route matching used by buildLocation:
remove references to accumulating `search` and the older list of skipped items
(AbortController, ControlledPromise, loaderDeps, full match objects), and
instead state precisely that this lightweight match only returns
`matchedRoutes`, `fullPath`, and `params`. Locate and edit the comment blocks
around the `buildLocation`/lightweight matching implementation (the docblocks
near the current lines ~1690 and ~1808) to reflect the new return shape and
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/router-core/src/router.ts`:
- Around line 1844-1849: The code sets fromSearch = currentLocation.search which
feeds the live mutable search into middleware/updater chain; instead capture and
use the validated/defaulted search snapshot used by validateSearch (not the live
object) — e.g., build fromSearch the same way fromParams is created (a shallow
copy of the validated search snapshot from lightweightResult or the cached
validatedSearch), and ensure search.middlewares and dest.search receive that
copied snapshot so in-place mutations cannot leak back into router state.

---

Nitpick comments:
In `@packages/router-core/src/router.ts`:
- Around line 1690-1693: Update the docstrings that describe the lightweight
route matching used by buildLocation: remove references to accumulating `search`
and the older list of skipped items (AbortController, ControlledPromise,
loaderDeps, full match objects), and instead state precisely that this
lightweight match only returns `matchedRoutes`, `fullPath`, and `params`. Locate
and edit the comment blocks around the `buildLocation`/lightweight matching
implementation (the docblocks near the current lines ~1690 and ~1808) to reflect
the new return shape and behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 230c6370-10f4-4301-9458-594d504e2f0f

📥 Commits

Reviewing files that changed from the base of the PR and between b9f9ea1 and b745f7d.

📒 Files selected for processing (1)
  • packages/router-core/src/router.ts

@codspeed-hq
Copy link

codspeed-hq bot commented Mar 22, 2026

Merging this PR will not alter performance

✅ 6 untouched benchmarks


Comparing flo/current-location-lightweight-cache (b845204) with main (b9f9ea1)

Open in CodSpeed

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