Skip to content

perf(claims): trust server link state at hydration - #684

Merged
ryansolid merged 1 commit into
nextfrom
claude/project-thread-271xwi
Oct 14, 2026
Merged

ryansolid merged 1 commit into
nextfrom
claude/project-thread-271xwi

Conversation

@ryansolid

Copy link
Copy Markdown
Member

Before: every anchor claimed during hydration resolves its href with new URL() and runs the link matcher, even when the server already rendered its aria-current/data-active. The HN story page has 1,414 claimed anchors. On the SPA twin with a client toggle per comment, the claim handler takes about 55 ms at load at 4x CPU (median of 7, timed inside the handler).

After: when the document carries server link state (hasServerLinkState()), a hydrating anchor is registered without being resolved. The registry sweep resolves it on the first routing change. An anchor the server skipped because of an authored aria-current is still resolved at claim, as before. So are client-created anchors and pages rendered without server link state.

This is the deferral proposed in #665. It was declined there because server HTML could not carry link state; #654 / #668 made that possible, but the claim never started trusting it.

How: setupLinkClaims skips the claim-time refresh when serverState && isHydrating() and the anchor either has no aria-current or carries the router's own (rec.owned). The rec.owned expression is unchanged, only rewritten to reuse the hydrating flag.

Measured on the HN SPA toggle variant by patching the built bundle the same way (/stories/30186326, 4x CPU, cold):

  • Sampled JS self time: about 497 → 379 ms (two alternating batches of 5).
  • Trace script bucket: 299 → 256 ms (median of 9).
  • This matches stubbing the claim handler out entirely.

The SC twin gets the same saving on its hydrated anchors.

Tests: hydration-link-state.spec.ts now records which anchor hrefs are resolved against the document during hydration and expects only the authored anchor. It failed before this change (it saw /, /about, and the absolute link) and passes after. The existing assertions on the state after hydration, after navigation, and on the late streamed anchor still pass. npm test: 707 + 96 pass, and types pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LZkrATgvFJjgFRdDB4Drcq


Generated by Claude Code

When the server rendered link state (`hasServerLinkState()`), a hydrating
anchor already carries `aria-current`/`data-active` for the location being
hydrated, so the claim registers it without resolving its URL. The sweep
resolves it on the first routing change. Anchors the server skipped for an
authored `aria-current` are still resolved at claim. This is the deferral
#665 proposed, made correct by the server link state from #654.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZkrATgvFJjgFRdDB4Drcq
@changeset-bot

changeset-bot Bot commented Oct 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3832cd5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@solidjs/router Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@ryansolid
ryansolid marked this pull request as ready for review October 14, 2026 07:44
@ryansolid
ryansolid merged commit cce9fe2 into next Oct 14, 2026
4 checks passed
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