Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
7b0f68c
perf(router-core): reduce empty location pipeline work
schiller-manuel Sep 5, 2026
85a9fc4
perf(react-router): avoid redundant Link state prop allocations
schiller-manuel Sep 5, 2026
4090ba6
perf(router-core): share pathname interpolation across Links
schiller-manuel Sep 5, 2026
13254ba
test(router-core): cover shared interpolation cache workloads
schiller-manuel Sep 5, 2026
0f17e6b
perf(router-core): reuse interpolation pass for cache keys
schiller-manuel Sep 5, 2026
0f21fd1
perf(router): meet the original Link bundle budgets
schiller-manuel Sep 5, 2026
87aabae
test(router): add opt-in React Link performance cases
schiller-manuel Sep 5, 2026
4cb5df2
test(router): stabilize opt-in Link performance comparisons
schiller-manuel Sep 5, 2026
5e89194
perf(router): compact Solid and Vue Link state props
schiller-manuel Sep 5, 2026
ac6d16d
refactor(router-core): clarify segment interpolation
schiller-manuel Sep 5, 2026
2d3fd1c
fix(router): honor Link state props and Vue class bindings
schiller-manuel Sep 5, 2026
13408a7
fix(router-core): respect router mode in empty search shortcut
schiller-manuel Sep 5, 2026
fbfc561
fix(router-core): preserve search mode tree-shaking
schiller-manuel Sep 6, 2026
e4ca6a9
perf(router-core): compact navigation parameter resolution
schiller-manuel Sep 6, 2026
8a9f57e
fix(solid-router): preserve computed Link routing props
schiller-manuel Sep 7, 2026
29509b9
refactor(router-core): consolidate path interpolation
schiller-manuel Sep 7, 2026
ec05cc6
perf(router-core): keep derived caches on routes
schiller-manuel Sep 7, 2026
e0b7e9f
test(router-core): add opt-in matching interpolation benchmarks
schiller-manuel Sep 8, 2026
b58ebfb
fix(vue-router): scope functional-render subscriptions
schiller-manuel Sep 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bright-dryers-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/router-core': patch
---

Cache route branches and interpolated paths on their route objects, removing the fixed template limit for registered routes and reusing cached paths across server requests. Rebuild tree-dependent caches together, preserve decoder and trailing-slash isolation, and keep unregistered templates bounded.
5 changes: 5 additions & 0 deletions .changeset/fancy-items-greet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/router-core': patch
---

Respect the router instance's effective server mode when applying the empty-search middleware shortcut. Preserve client search reuse, inherited search identity, and middleware-chain behavior.
5 changes: 5 additions & 0 deletions .changeset/forty-loops-sing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/solid-router': patch
---

Keep Solid Link's computed `href`, `target`, and `disabled` values authoritative when active or inactive state props contain routing options, while preserving state-prop refs, event handlers, and class/style merging.
5 changes: 5 additions & 0 deletions .changeset/honest-nails-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/react-router': patch
---

React Links resolve state props without temporary class-name arrays or unnecessary style copies.
5 changes: 5 additions & 0 deletions .changeset/loud-times-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/router-core': patch
---

Collect shared pathname-cache keys during interpolation instead of parsing templates twice, and share dynamic-segment handling to reduce bundle size.
7 changes: 7 additions & 0 deletions .changeset/loud-weeks-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@tanstack/react-router': patch
'@tanstack/solid-router': patch
'@tanstack/vue-router': patch
---

Allow active and inactive Link props to override base element props in React and Solid while preserving class/style merging. Keep React's `href`, `target`, and `disabled` values controlled by routing options. Preserve Vue object and nested-array class bindings, including reactive updates and server rendering, without mutating cached bindings during VNode normalization.
5 changes: 5 additions & 0 deletions .changeset/lucky-candies-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/router-core': patch
---

Simplify path interpolation into ordered segment-type blocks with shared prefix/value/suffix assembly. Preserve one-pass parsing and optional metadata collection, and reuse the public result object instead of allocating and clearing a missing-parameter callback.
5 changes: 5 additions & 0 deletions .changeset/mean-mice-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/router-core': patch
---

Reuse pathname interpolation across Links with bounded router-scoped caches. Keep parameter callbacks and navigation state independent of the cache.
5 changes: 5 additions & 0 deletions .changeset/quick-otters-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/router-core': patch
---

Reduce duplication in navigation parameter handling while preserving parameter inheritance, null-prototype dictionaries, and updater isolation.
5 changes: 5 additions & 0 deletions .changeset/shaggy-foxes-speak.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/router-core': patch
---

Preserve compile-time tree-shaking of the empty-search server-mode check while retaining the router instance mode as the development fallback.
6 changes: 6 additions & 0 deletions .changeset/six-adults-open.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@tanstack/solid-router': patch
'@tanstack/vue-router': patch
---

Reduce Link bundle size by sharing exact/fuzzy active-path checks and resolving only the selected active or inactive props. Avoid unnecessary class/style allocations while preserving reactive style updates, server rendering, and each framework's prop-override behavior.
5 changes: 5 additions & 0 deletions .changeset/slick-forks-beam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/router-core': patch
---

Reduce location-building overhead for object-form path params and empty search middleware pipelines.
6 changes: 6 additions & 0 deletions .changeset/smart-corners-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@tanstack/router-core': patch
'@tanstack/router-devtools-core': patch
---

Consolidate internal path interpolation into `interpolatePath`, returning a pathname directly and collecting metadata only when requested. Update router and devtools callers without changing route parsing or interpolation caching.
6 changes: 6 additions & 0 deletions .changeset/spotty-bats-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@tanstack/router-core': patch
'@tanstack/react-router': patch
---

Reduce the bundle cost of shared Link pathname interpolation while preserving its rendering performance. Reuse one interpolation pass for pathname and optional metadata, keep the bounded cache on the router, and simplify React Link active-state and prop merging.
5 changes: 5 additions & 0 deletions .changeset/three-hornets-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/vue-router': patch
---

Clean up store subscriptions and Link preload effects created during functional component renders before rerendering or unmounting.
97 changes: 97 additions & 0 deletions benchmarks/client-nav/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,100 @@ Typecheck benchmark sources (baseline + scenarios):
```bash
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/client-nav:test:types --outputStyle=stream --skipRemoteCache
```

## Opt-in React Link performance suite

`link-performance/` contains additional client-navigation and SSR workloads for
focused Link work. They are **not included** in the regular client-nav/SSR
aggregate projects or their CodSpeed build dependencies. Benchmark discovery
also requires `TSR_LINK_PERF=1`; without it, no extended benchmark files or app
bundles are imported.

```bash
TSR_LINK_PERF=1 CI=1 NX_DAEMON=false pnpm nx run @benchmarks/react-link-performance:test:perf:client --outputStyle=stream --skipRemoteCache -- --run
TSR_LINK_PERF=1 CI=1 NX_DAEMON=false pnpm nx run @benchmarks/react-link-performance:test:perf:ssr --outputStyle=stream --skipRemoteCache -- --run

# Select a feature and save the normal Vitest JSON report.
TSR_LINK_PERF=1 CI=1 NX_DAEMON=false pnpm nx run @benchmarks/react-link-performance:test:perf:client --outputStyle=stream --skipRemoteCache -- --run -t "updater|optional|splat" --outputJson /tmp/link-perf.json
```

The cases cover repeated versus unique destination params, updater functions,
relative/inherited values, search middleware chains, param stringification,
optional and splat segments, encoding, masks, basepath/rewrites, and active
props with structured search. These exercise different costs: cache hits and
misses, parameter cloning, callbacks, middleware traversal, URI encoding,
building masked/public locations, active-state comparisons, and prop merging.
Existing preload, mount, and route-tree-scale scenarios remain responsible for
those separate workloads.

- **Client:** 200 persistent measured Links, four control Links, and eight
completed navigations per timed batch. The existing client harness checks
hrefs and active state during its untimed warm-up lap. Control navigations
replace the history entry, keeping history size constant. Post-measurement
assertions also check that the measured anchors stayed mounted.
- **SSR:** four fresh-router requests per timed batch, each rendering 200
measured Links through `RouterProvider` and `renderToString`. Router creation,
`router.load()`, rendering, and history cleanup are included. This isolates
Router SSR Link work, not Start HTTP handling, dehydration, or streaming.
HTML assertions run outside the timed batch.
- Both use the same code-based workload definitions and production JSX/library
builds. The regular Vitest entry points use at least 100 warm-up iterations,
one second of warm-up time, and five-second measurement windows. The client
and server bundles assert their resolved `isServer` environment. React and
React DOM remain external so comparisons can share the same renderer runtime.
These bundles are Node-hosted (jsdom for client mode), not browser deployments.

Run the gate tests and typecheck separately:

```bash
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/react-link-performance:test:unit --outputStyle=stream --skipRemoteCache
CI=1 NX_DAEMON=false pnpm nx run @benchmarks/react-link-performance:test:types --outputStyle=stream --skipRemoteCache
```

For before/after comparisons, use identical benchmark files and dependencies
on both refs, build each ref through its Nx targets, and alternate fresh
Vitest processes. Report the actual refs, per-case means and relative margins
of error; rerun noisy or borderline results with `-t` rather than interpreting
a small difference as a proven speedup. Client and SSR times have different
batch units and should not be compared directly.

### Stable paired comparisons

For regression decisions, prefer the paired runner over a whole-file Vitest
run. It starts a fresh process for every case and repetition, avoiding JIT
feedback from earlier cases. Inside each process, both revisions share the
same production React installation but have separate router/app modules and
router instances. Initialization order alternates between repetitions.

```bash
# Build the baseline using these same benchmark sources in its own checkout.
# --baseline points to that checkout's link-performance/dist directory.
TSR_LINK_PERF=1 pnpm nx run @benchmarks/react-link-performance:test:perf:stable -- \
--baseline /path/to/baseline/benchmarks/client-nav/link-performance/dist \
--outputJson /tmp/paired-links.json

# Narrow a comparison, or increase independent process repetitions.
TSR_LINK_PERF=1 pnpm nx run @benchmarks/react-link-performance:test:perf:stable -- \
--baseline /path/to/baseline/benchmarks/client-nav/link-performance/dist \
--mode ssr -t "middleware|unique-params" --repeats 6 \
--outputJson /tmp/paired-links-ssr.json
```

The runner requires Node with `process.threadCpuUsage` (Node 24 works).
It fixes V8 random/hash seeds, warms each variant for at least two seconds
and 100 batches, then alternates ABBA/BAAB blocks calibrated to roughly 500 ms.
Both variants do exactly the same number of batches per block. It records
main-thread CPU time, wall time, and whole-process CPU time; GC during
measurement is not disabled or discarded.

The default is four independent process repetitions. Reported 95% intervals
use their paired log-ratios, not the many correlated batches as independent
samples. A faster/slower verdict requires CPU and wall intervals to agree.
Intervals overlapping zero or disagreeing metrics are inconclusive; narrow
intervals entirely inside +/-2% are reported separately.

An A/A calibration uses the current `dist` directory as `--baseline`. Its
intervals should contain zero before trusting similarly sized A/B differences.
Shared-machine contention can still make small changes unresolved. Do not
interpret a point estimate alone, or an inconclusive result, as proof that
a workload is unchanged.
Loading
Loading