Repository navigation
perf(claims): skip URL parsing for plain hrefs in SSR link claims - #701
Conversation
🦋 Changeset detectedLatest commit: ea3d8ba The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
|
Thanks, the server win is real and we'd like to take this. One change before merging: client bytes matter a lot for the router, and the Could you gate it on // src/utils.ts
import { isServer } from "@solidjs/web";
// ...
return (isServer && isPlainPath(p) ? p : new URL(mockBase + p).pathname).toLowerCase().replace(/\/$/, "");Measured on a small router app (vite 8 production build, same solid rc.19 for both), compared with
The client tests and the fuzz test still pass with the gate. A rebase onto Generated by Claude Code |
4024a6a to
ea3d8ba
Compare
|
Done. The |
Server link claims (from #668) made SSR of link-heavy pages noticeably slower. On a 1000-row table with one
<a href="/server-side-rendered/:id">per row (theapp-solidport of the e18e framework-tracker benchmark app), the claims cost about 1.2 ms of CPU per request. That is the difference between 283 and 460 req/s.Cause
For every anchor,
setupServerLinkClaimsresolves the href withnew URL(href, page), thenlinkMatchercallscomparablePath, which parses the pathname again withnew URL. Two WHATWG URL parses per link dominated the claim cost.linkMatcheralso split the target on#and scanned for?for every link, though the query only matters for an exact match.Change
plainHref(href)inutils.tsreads the pathname, search and comparable path of a root-relative href withoutURL. It applies only when the path and query use charactersURLkeeps unchanged (ASCII word characters and-.~!$&'()*+,;=:@/, plus?in the query but not') and the path has no./..segments. Every other href (%,\, non-ASCII, whitespace,//, dot segments, absolute URLs) still goes throughURL. It runs only underisServer, where a root-relative href always resolves against the request origin, so client bundles drop it.comparablePathskips itsURLparse on the server when the normalized path is plain in the same sense. The skip is gated onisServertoo, so client bundles keep the plainURLparse.linkMatcheraccepts a precomputed comparable path and only parses the target's query when the paths are equal.Rendered HTML is byte-identical for the benchmark page (row content aside).
Verification
pnpm test(723 client tests, 105 server tests) andpnpm test:typespass on top ofnextwith rc.19.test/server/plain-href-fast-path.spec.tsfuzzes 50,000 random paths and hrefs built from charactersURLtreats specially, and checkscomparablePathandplainHrefagainstURL. It runs in the server suite because the client never reaches the fast path. A new case intest/server/ssr-link-state.spec.tsxcovers hrefs thatURLnormalizes: uppercase with a trailing slash, dot segments, an empty query, and percent-encoded vs raw non-ASCII./server-side-renderedof that app (solid-js/web rc.19, Node 24, M-series Mac), next.42 vs this branch packed into the same app. The first two runs were the same session, the last a later and slower one.isServerbranch incomparablePath: +11 B brotli on a small router app, as measured in review.Left open
The remaining ~0.4 ms per 1000 links is mostly the per-anchor attrs object that
@solidjs/webbuilds forssrLinkClaim, plus the matcher's result objects. Cutting that would need a change in@solidjs/web's claim hook.🤖 Generated with Claude Code