98ae4cf2 perf(router): keep the location cache client-only and slim Link SSR
The per-options location cache from the location-reuse changes only ever
hits on the client: server renders never repeat an options object, yet
every server build still created the WeakMap, looked it up per build, and
Solid/Vue server Links stored an entry per render. All three sites are now
guarded by `!(isServer ?? this.isServer)`. `isServer` is a per-bundle
constant, so server bundles contain no cache at all (the Link SSR bundle
has zero references); client bundles keep it unchanged.
Rendering a React Link on the server copied its props four times: `Link`
split off `_asChild`, `useLinkProps` split the router options off with an
object rest, the result was assembled with spreads, and `Link` copied once
more to drop `type` and `disabled`. Profiling showed the object rest alone
was 85% of the hook's self time: V8 checks every key against the whole
35-entry exclusion list (about 470 ns per call versus 95 ns for a key-set
copy).
`useLinkProps` is now a wrapper over `useLinkPropsFor(options, ref, host)`.
`Link` passes its host (`'a'` or the `createLink` component), so the hook
omits `disabled` for anchors and `type` for both, and `Link` passes the
result straight to `createElement`. The server branch is a separate
`getServerLinkProps` referenced only inside the `isServer` check, so it and
its key set are dropped from client bundles; it reads the few options it
needs, splits element props with the key set, fills the props object in
place with the same precedence and attribute order as before, and no
longer runs `useForwardedRef` (moved after the server return). The client
keeps its rest destructure; the host handling costs a few bytes there,
mostly paid back by the folded router-core guards (numbers below).
`encodePathLikeUrl` tests with one merged character class
(`/[\s\u0080-\uFFFF]/`), which matches exactly the same code units as the
alternation it replaces and is about 10% cheaper.
Measurements (macOS arm64, Node 24.8.0, local):
- Link SSR paired runner (ABBA, 4 fresh processes per case) against the
previous stack top: all 13 cases faster, CPU -19.5% to -40.3%.
- Link client paired runner: all 13 cases faster, CPU -4.6% to -9.2%.
- Start SSR request loop (benchmarks/ssr, react), interleaved builds:
3.120 -> 2.890 ms per loop (-7.4%), 320.5 -> 346.1 hz.
- Client bundle react-router.minimal gzip: 86012 -> 86021 (+9; raw +7,
brotli -22): the router-core guards fold to -12, the Link host handling
costs +19. The Link SSR bundle shrinks from 197832 to 189989 bytes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>