Skip to content

perf(router-core): match the current location lazily in buildLocation - #8406

Merged
schiller-manuel merged 1 commit into
schiller-manuel-link-memo-layerfrom
schiller-manuel-lazy-current-match-layer
Sep 13, 2026
Merged

schiller-manuel merged 1 commit into
schiller-manuel-link-memo-layerfrom
schiller-manuel-lazy-current-match-layer

Conversation

@schiller-manuel

@schiller-manuel schiller-manuel commented Sep 12, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #8405.

🎯 Changes

buildLocation matched the current location up front for every build (matchRoutesLightweight: three store reads and a WeakMap lookup), although the result is only read by relative destinations, inherited params, search: true or updater functions, and search middlewares. An absolute destination with its params given, on a branch without search middlewares, never reads it, yet still paid for the match on every call.

  • The lightweight match is now computed on first read inside currentMatch() (lightweight ??= this.matchRoutesLightweight(currentLocation)).
  • The destination template is scanned for $ once (isTemplate) instead of twice.

Behavior is unchanged: every read of the current match still goes through currentMatch(), which also sets usedCurrent, so the client-side location cache keeps excluding builds that depend on the current location. Public API unchanged. One file touched: packages/router-core/src/router.ts (+7/−7).

Measurements (local, macOS arm64, Node 24.8.0)

  • Server buildLocation of /items/$itemId with params on a middleware-free tree: 399 → 382 ns per call (−4%).
  • Builds that read the current match are unchanged. This includes every case of the Link benchmark, whose root route has search middlewares, so the paired Link runner cannot see this change.
  • react-router.minimal gzip: 86008 → 86012 (+4 bytes, exactly the stack base).

Tests run (fresh Nx runs, --skipNxCache --skipRemoteCache)

  • @tanstack/router-core:test:unit — 133 files, 3303 passed, 4 expected fail
  • @tanstack/router-core:test:types — TS 5.6, 5.7, 5.8, 5.9, 6.0, 7.0 all pass
  • @tanstack/router-core:test:eslint — 0 errors (27 pre-existing warnings, none in the changed region)
  • @tanstack/react-router:test:unit — 92 files, 1175 passed, 1 skipped
  • @tanstack/solid-router:test:unit — 68 files, 942 passed, 1 skipped (+ 7 files, 51 passed)
  • @tanstack/vue-router:test:unit — 71 files, 968 passed, 1 skipped
  • git diff --check HEAD~1 and pnpm exec prettier --check packages/router-core/src/router.ts — clean

✅ 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. The existing changeset .changeset/brisk-links-serve.md from the lower layers of this stack already declares a @tanstack/router-core patch, so no new changeset is added here.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • Performance
    • Improved internal route-building efficiency by deferring lightweight route matching until needed.
    • Reduced repeated template checks during route construction.
  • Behavior
    • No user-visible behavior changes.

@coderabbitai

coderabbitai Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: fdf595ce-6209-43e9-8dc7-75b4f419ead4

📥 Commits

Reviewing files that changed from the base of the PR and between 505c9ca and 4e93585.

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

Included review availability: Your plan provides up to 10 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

buildLocation now computes the lightweight route match only when currentMatch() reads it. The template check is computed once and reused for branch selection and interpolation parsing. Behavior remains unchanged.

Changes

Location build evaluation

Layer / File(s) Summary
Lazy route matching and template reuse
packages/router-core/src/router.ts
The build closure lazily initializes the lightweight route match. It reuses one isTemplate value for destination routing and interpolation parsing.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Refactor

Suggested reviewers: sheraff

Merge Risk: ⚪ Minimal · up to 1f197

The lazy matching and template-check changes preserve existing behavior, with no actionable merge-blocking risk identified.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary performance change: lazy matching of the current location in buildLocation.
Description check ✅ Passed The description follows the repository template, explains the motivation and implementation, documents behavior and performance impact, lists tests, and completes the checklist and release-impact sect…
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 schiller-manuel-lazy-current-match-layer

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.

@schiller-manuel
schiller-manuel added this pull request to stack #8346 September 12, 2026 14:18
@nx-cloud

nx-cloud Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 1f197ad

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

☁️ Nx Cloud last updated this comment at 2026-09-12 22:45:13 UTC

@pkg-pr-new

pkg-pr-new Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/eslint-plugin-start

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-rsc

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

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

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: 1f197ad

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

7 package(s) bumped directly, 22 bumped as dependents.

🟩 Patch bumps

Package Version Reason
@tanstack/history 1.162.3 → 1.162.4 Changeset
@tanstack/react-router 1.170.35 → 1.170.36 Changeset
@tanstack/router-core 1.171.29 → 1.171.30 Changeset
@tanstack/router-devtools-core 1.168.1 → 1.168.2 Changeset
@tanstack/solid-router 1.170.33 → 1.170.34 Changeset
@tanstack/start-server-core 1.169.34 → 1.169.35 Changeset
@tanstack/vue-router 1.170.32 → 1.170.33 Changeset
@tanstack/react-router-devtools 1.167.1 → 1.167.2 Dependent
@tanstack/react-start 1.168.52 → 1.168.53 Dependent
@tanstack/react-start-client 1.168.33 → 1.168.34 Dependent
@tanstack/react-start-rsc 0.1.51 → 0.1.52 Dependent
@tanstack/react-start-server 1.167.40 → 1.167.41 Dependent
@tanstack/router-cli 1.167.35 → 1.167.36 Dependent
@tanstack/router-devtools 1.167.1 → 1.167.2 Dependent
@tanstack/router-generator 1.167.35 → 1.167.36 Dependent
@tanstack/router-plugin 1.168.37 → 1.168.38 Dependent
@tanstack/router-vite-plugin 1.167.37 → 1.167.38 Dependent
@tanstack/solid-router-devtools 1.167.1 → 1.167.2 Dependent
@tanstack/solid-start 1.168.50 → 1.168.51 Dependent
@tanstack/solid-start-client 1.168.32 → 1.168.33 Dependent
@tanstack/solid-start-server 1.167.39 → 1.167.40 Dependent
@tanstack/start-client-core 1.170.29 → 1.170.30 Dependent
@tanstack/start-plugin-core 1.171.42 → 1.171.43 Dependent
@tanstack/start-static-server-functions 1.167.34 → 1.167.35 Dependent
@tanstack/start-storage-context 1.167.31 → 1.167.32 Dependent
@tanstack/vue-router-devtools 1.167.1 → 1.167.2 Dependent
@tanstack/vue-start 1.168.49 → 1.168.50 Dependent
@tanstack/vue-start-client 1.167.35 → 1.167.36 Dependent
@tanstack/vue-start-server 1.167.39 → 1.167.40 Dependent

@github-actions

github-actions Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Bundle Size Benchmarks

  • Commit: d26a07c8c00f
  • Measured at: 2026-09-12T22:35:42.423Z
  • Baseline source: history:ae6853592904
  • 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 84.0 KiB
+20 B
83.9 KiB
+17 B
261.7 KiB
-648 B
73.2 KiB
+52 B
█████▁▁▁▁▁▁▂
react-router.full 87.5 KiB
+17 B
87.3 KiB
+15 B
273.4 KiB
-631 B
76.2 KiB
+13 B
█████▂▂▂▂▂▁▂
solid-router.minimal 33.4 KiB
-28 B
33.3 KiB
-29 B
95.9 KiB
-841 B
30.3 KiB
+19 B
▆█████████▇▁
solid-router.full 38.3 KiB
-5 B
38.2 KiB
-4 B
110.6 KiB
-841 B
34.5 KiB
+47 B
▅█████████▄▁
vue-router.minimal 49.5 KiB
-71 B
49.4 KiB
-72 B
137.1 KiB
-1.3 KiB
44.8 KiB
-80 B
█████▃▃▃▃▃▂▁
vue-router.full 55.1 KiB
-93 B
55.0 KiB
-90 B
155.3 KiB
-1.3 KiB
49.7 KiB
-111 B
█████▃▃▃▃▃▃▁
react-start.minimal 96.9 KiB
+64 B
96.8 KiB
+63 B
304.0 KiB
-648 B
84.1 KiB
+35 B
█████▁▁▁▁▁▁▄
react-start.query-integration 104.3 KiB
+35 B
104.1 KiB
+33 B
330.4 KiB
-655 B
90.5 KiB
+124 B
█████▁▁▁▁▁▁▃
react-start.deferred-hydration 97.7 KiB
+44 B
96.8 KiB
+48 B
305.3 KiB
-648 B
84.7 KiB
-1 B
█████▁▁▁▁▁▁▃
react-start.full 100.1 KiB
+46 B
100.0 KiB
+47 B
313.7 KiB
-625 B
86.8 KiB
+99 B
█████▁▁▁▁▁▁▃
react-start.rsbuild.minimal 100.2 KiB
+95 B
100.0 KiB
+95 B
314.4 KiB
-469 B
86.4 KiB
-58 B
█████▁▁▁▁▁▁▆
react-start.rsbuild.minimal-iife 100.6 KiB
+98 B
100.5 KiB
+98 B
315.4 KiB
-452 B
86.9 KiB
+103 B
█████▁▁▁▁▁▁▆
react-start.rsbuild.full 103.5 KiB
+79 B
103.3 KiB
+79 B
324.5 KiB
-430 B
89.3 KiB
+178 B
█████▁▁▁▁▁▁▅
solid-start.minimal 46.4 KiB
+30 B
46.2 KiB
+28 B
137.1 KiB
-841 B
41.2 KiB
+46 B
▄▂▂▂▂▂▂▂▂▂▁█
solid-start.deferred-hydration 49.4 KiB
+15 B
46.3 KiB
+16 B
144.4 KiB
-842 B
44.1 KiB
+88 B
▁▃▃▃▃▃▃▃▃▃▄█
solid-start.full 51.4 KiB
+24 B
51.3 KiB
+21 B
152.5 KiB
-836 B
45.6 KiB
+95 B
▃▂▂▂▂▂▂▂▂▂▁█
vue-start.minimal 65.6 KiB
-102 B
65.5 KiB
-101 B
187.9 KiB
-1.3 KiB
58.5 KiB
-10 B
█████▃▃▃▃▃▃▁
vue-start.full 69.5 KiB
-66 B
69.4 KiB
-63 B
200.2 KiB
-1.3 KiB
61.8 KiB
+52 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.

@codspeed

codspeed Bot commented Sep 12, 2026 •

Copy link
Copy Markdown

Merging this PR will improve performance by 12.83%

⚠️ 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

⚡ 45 improved benchmarks
✅ 135 untouched benchmarks

Performance Changes

Mode Benchmark BASE HEAD Efficiency
⚡ Simulation client-links navigation loop (react) 218.4 ms 97.6 ms ×2.2
⚡ Memory mem client interrupted-navigations (react) 385.3 KB 213 KB +80.84%
⚡ Memory mem client interrupted-navigations (vue) 389.7 KB 222.8 KB +74.93%
⚡ Memory mem client interrupted-navigations (solid) 384.6 KB 253.4 KB +51.78%
⚡ Simulation client-rewrites navigation loop (react) 110.1 ms 79.4 ms +38.68%
⚡ Simulation client-history navigation loop (react) 84.5 ms 69.7 ms +21.33%
⚡ Simulation client-links navigation loop (vue) 347.1 ms 296.4 ms +17.11%
⚡ Simulation client-route-tree-scale navigation loop (react) 128.8 ms 111.2 ms +15.88%
⚡ Simulation client-links navigation loop (solid) 524.1 ms 456.3 ms +14.85%
⚡ Simulation client-async-pipeline navigation loop (vue) 87.5 ms 77.7 ms +12.64%
⚡ Simulation client-route-tree-scale navigation loop (vue) 161.4 ms 144 ms +12.04%
⚡ Simulation client-async-pipeline navigation loop (react) 75.7 ms 67.6 ms +11.94%
⚡ Simulation client-control-flow navigation loop (vue) 83.6 ms 75.2 ms +11.07%
⚡ Simulation ssr request loop (react) 177.3 ms 160.9 ms +10.21%
⚡ Simulation client-loaders navigation loop (react) 105.9 ms 97.6 ms +8.47%
⚡ Simulation client-side navigation loop (react) 108.9 ms 100.9 ms +7.96%
⚡ Memory mem client loader-data-retention (react) 949.3 KB 879.9 KB +7.89%
⚡ Memory mem server error-paths redirect (react) 863.4 KB 806.7 KB +7.03%
⚡ Simulation client-search-params navigation loop (react) 142.4 ms 133.9 ms +6.3%
⚡ Memory mem client loader-data-retention (vue) 979 KB 921.4 KB +6.26%
... ... ... ... ... ...

ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing schiller-manuel-lazy-current-match-layer (1f197ad) with main (ae68535)1

Open in CodSpeed

Footnotes

  1. No successful run was found on schiller-manuel-link-memo-layer (a0e2ef3) during the generation of this report, so main (ae68535) was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

`buildLocation` matched the current location up front for every build,
although the result is only read by relative destinations, inherited
params, `search: true` or updater functions and search middlewares. An
absolute destination with its params given, on a branch without search
middlewares, never reads it, yet still paid three store reads and a
WeakMap lookup per call. The match is now computed on first read, and the
destination template is scanned for `$` once instead of twice.

Server `buildLocation` of `/items/$itemId` with params on a middleware-free
tree: 399 -> 382 ns per call (-4%). Builds that read the current match are
unchanged. react-router.minimal gzip +4 (86008 -> 86012, the stack base).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@schiller-manuel
schiller-manuel force-pushed the schiller-manuel-lazy-current-match-layer branch from 4e93585 to 1f197ad Compare September 12, 2026 21:35
@schiller-manuel
schiller-manuel merged commit 1e6d9c8 into main Sep 13, 2026
26 checks passed
@schiller-manuel
schiller-manuel deleted the schiller-manuel-lazy-current-match-layer branch September 13, 2026 11:55
Sheraff pushed a commit that referenced this pull request Sep 14, 2026
…#8406)

`buildLocation` matched the current location up front for every build,
although the result is only read by relative destinations, inherited
params, `search: true` or updater functions and search middlewares. An
absolute destination with its params given, on a branch without search
middlewares, never reads it, yet still paid three store reads and a
WeakMap lookup per call. The match is now computed on first read, and the
destination template is scanned for `$` once instead of twice.

Server `buildLocation` of `/items/$itemId` with params on a middleware-free
tree: 399 -> 382 ns per call (-4%). Builds that read the current match are
unchanged. react-router.minimal gzip +4 (86008 -> 86012, the stack base).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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.

2 participants