Skip to content

perf(solid-router): proxy-free link props in the spread hot path#7609

Open
brenelz wants to merge 1 commit into
TanStack:solid-router-v2-prefrom
brenelz:perf/solid-router-proxy-free-link-props
Open

perf(solid-router): proxy-free link props in the spread hot path#7609
brenelz wants to merge 1 commit into
TanStack:solid-router-v2-prefrom
brenelz:perf/solid-router-proxy-free-link-props

Conversation

@brenelz

@brenelz brenelz commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Problem

Comparing the CodSpeed flamegraphs for the client-side navigation benchmarks showed Solid ~18% slower than React (66.2 ms vs 55.9 ms), but with an odd shape: Solid's attributable JS time was actually lower than React's. The entire gap sat in an unattributed NodeJS internals bucket — 18.3 ms (27.7%) for Solid vs 2.5 ms (4.5%) for React.

The cause: useLinkProps layered four proxies — merge() for the activeProps/inactiveProps defaults, two stacked splitProps/omit proxies, and a final merge(propsSafeToSpread, resolvedProps). Solid's spread() re-enumerates the returned object on every update, so every navigation walked ownKeys/getOwnPropertyDescriptor/get traps through all four layers, for every <Link> on the page. V8 dispatches proxy traps in C++ runtime code, which profilers can't attribute to JS frames — hence the opaque "internals" cost.

Change

  • useLinkProps now returns a plain object with a stable key set. Reactivity lives in property getters backed by fine-grained memos (href, data-status, aria-current, data-transitioning, class, style, disabled/role/aria-disabled). Values that no longer apply resolve to undefined, which spread()/assign() treats as attribute removal — DOM output is unchanged.
  • Defaults for activeProps/inactiveProps are resolved through accessors at the use sites instead of a merge() proxy; the two stacked omit proxies became a single one-time descriptor copy.
  • The built-location memo (next) gained href-based output equality, so downstream memos skip recompute when a navigation doesn't change a link's target.

Results

Local client-nav benchmark (same machine, NODE_ENV=production):

mean / iteration
solid before 7.06 ms
solid after 4.80 ms (−32%)
react (reference) 5.96 ms

In the local CPU profile the spread-effect hotspot dropped from 14.3% cumulative to 1.0% and proxy enumeration (ownKeys/keys) left the hotspot list entirely. Bundle impact: +0.2 kB gzipped.

  • 813/813 solid-router tests pass (incl. 127 link tests), typecheck clean.
  • Verified end-to-end in the built benchmark app: default + custom activeProps/inactiveProps classes, data-status/aria-current, and inherited-search hrefs all update correctly across navigations.

Behavioral note

Keys returned by activeProps/inactiveProps functions are discovered once at setup; a function that later returns brand-new keys (beyond the initial set plus class/style) won't have those keys applied. Nothing in the test suite relies on that pattern, but it's the one semantic trade for keeping the spread target's key set stable.

🤖 Generated with Claude Code

useLinkProps stacked four proxies (merge of defaults, two omit proxies,
and a final merge with the resolved-props memo), and Solid's spread()
re-enumerated them through V8 proxy traps on every navigation for every
Link. Return a plain object with a stable key set instead, with
reactivity in property getters backed by fine-grained memos, and add
href-based equality to the built-location memo.

Client-side navigation benchmark: 7.06ms -> 4.80ms per iteration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 17c71d34-067b-4c8a-b5ee-d4ba249d0d70

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@nx-cloud

nx-cloud Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit e3e4aaf

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 4m 39s View ↗

☁️ Nx Cloud last updated this comment at 2026-06-12 01:17:00 UTC

@pkg-pr-new

pkg-pr-new Bot commented Jun 12, 2026

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/eslint-plugin-start

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-rsc

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

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

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: e3e4aaf

@codspeed-hq

codspeed-hq Bot commented Jun 12, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 17.32%

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

⚡ 2 improved benchmarks
❌ 1 regressed benchmark
✅ 2 untouched benchmarks
⏩ 1 skipped benchmark1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
ssr request loop (vue) 420.3 ms 481.2 ms -12.67%
client-side navigation loop (solid) 72.6 ms 44.7 ms +62.36%
ssr request loop (solid) 174.6 ms 153.3 ms +13.89%

Tip

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


Comparing brenelz:perf/solid-router-proxy-free-link-props (e3e4aaf) with solid-router-v2-pre (67a9040)2

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

  2. No successful run was found on solid-router-v2-pre (65133ed) during the generation of this report, so 67a9040 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants