Skip to content

fix(web): a shared server-component factory hydrates a slot at every mount (#3889) - #3933

Merged
ryansolid merged 5 commits into
nextfrom
fix-3889
Oct 9, 2026
Merged

ryansolid merged 5 commits into
nextfrom
fix-3889

Conversation

@ryansolid

Copy link
Copy Markdown
Member

Summary

A server component factory used at two sites rendered two frames under one function id, so both client counters shared one hydration key. The first mount claimed that key and the second button never became its own counter.

Each later site of the same function now gets its own frame scope for data-fid, hydration keys, and slot records. The function id stays the shared mount identity, and adoption walks those sites in document order. A page that mounts the function once is unchanged.

Fixes #3889

Public API

None

Test plan

  • Server-render one factory at two sites: two counters, distinct _hk keys (test/server/multisite-hydration-3889.spec.tsx)
  • Hydrate that document: both buttons stay and increment independently (test/hydration/multisite-hydration-3889.spec.tsx)
  • Nearby server-component, hydration, and frames consistency tests

Made with Cursor

…mount (#3889)

Co-authored-by: Cursor <cursoragent@cursor.com>
@changeset-bot

changeset-bot Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f67490a

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

This PR includes changesets to release 12 packages
Name Type
@solidjs/web Patch
@solidjs/babel-plugin Patch
@solidjs/diagnostics Patch
@solidjs/element Patch
@solidjs/h Patch
@solidjs/html Patch
test-integration Patch
todos-server-example Patch
@solidjs/compiler Patch
@solidjs/signals Patch
solid-js Patch
@solidjs/universal 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

@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Size (brotli, eager entry chunk)

scenario head vs base minified vs base minified vs recorded cap lazy chunks (not counted)
signals: core floor (createSignal/Memo/Effect/Root/flush) 7.44 KB 0 B 0 B +2 B 7.45 KB ✅
signals: + createStore 14.69 KB 0 B 0 B +11 B 14.70 KB ✅
signals: + isPending/latest 9.63 KB 0 B 0 B −4 B 9.65 KB ✅
app: render + one signal (the simple-app floor) 9.92 KB 0 B 0 B +2 B 9.93 KB ✅
app: hydrating (no stores) with Show/For/Loading/Errored/lazy 17.87 KB 0 B 0 B 0 B 17.91 KB ✅ lazy-page.js 0.04 KB
app: hydrating + every store primitive family 29.16 KB 0 B 0 B +63 B 29.19 KB ✅ lazy-page.js 0.04 KB
app: CSR with Show/For/Loading/Errored/lazy 12.93 KB 0 B 0 B 0 B 12.96 KB ✅ lazy-page.js 0.04 KB
app: CSR, observe tier (same app on the observe artifacts) 14.51 KB 0 B 0 B 0 B 14.53 KB ✅ lazy-page.js 0.04 KB
app: CSR, observe tier + attribution engine enabled 28.83 KB 0 B 0 B −1 B 28.89 KB ✅ lazy-page.js 0.04 KB
app: compiled floor (one template, one text hole, one delegated click) 10.12 KB 0 B 0 B +2 B 10.13 KB ✅
app: compiled CSR (JSX todo app: spread/merge/omit, events, class/style, keyed For, Show, Loading + lazy, store) 25.20 KB 0 B 0 B +64 B 25.24 KB ✅ stats.js 0.18 KB
app: compiled hydrating (the same JSX todo app through hydrate(), compiled hydratable) 31.17 KB 0 B 0 B +9 B 31.17 KB ⚠️ over by 2 B, 11 B minified headroom stats.js 0.20 KB
frames: eager client consumer (frames client + transport, lazy codec) 11.41 KB +25 B (+0.2%) +46 B 0 B 11.41 KB ✅
page: base server components (hydrating + dynamic + frames + sf reference) 34.20 KB +79 B (+0.2%) +46 B −5 B 34.22 KB ✅ assets.js 0.78 KB, bind.js 1.83 KB, decode.js 6.23 KB, lazy-page.js 0.04 KB, regions.js 0.80 KB, trace.js 8.25 KB, wire.js 0.93 KB
page: live server components (base + live/GET + action + isPending/latest) 37.90 KB +20 B (+0.1%) +46 B −11 B 37.94 KB ✅ assets.js 0.78 KB, bind.js 1.83 KB, decode.js 6.23 KB, lazy-page.js 0.04 KB, regions.js 0.80 KB, trace.js 8.24 KB, wire.js 0.93 KB
page: compiled base server components (the base page as JSX: templates with class/style/attributes/events, For/Show; no spread) 35.44 KB −72 B (−0.2%) +46 B −5 B 35.47 KB ✅ assets.js 0.78 KB, bind.js 1.83 KB, decode.js 6.23 KB, regions.js 0.80 KB, sc-comments.js 0.20 KB, trace.js 8.24 KB, wire.js 0.93 KB
page: compiled live server components (the compiled base page + live/GET + action + isPending/latest) 41.00 KB +21 B (+0.1%) +45 B −19 B 40.95 KB ⚠️ over by 49 B, 39 B minified headroom eager (counted): web.js 22.03 KB; assets.js 0.78 KB, bind.js 1.83 KB, decode.js 6.23 KB, regions.js 0.79 KB, sc-comments.js 0.19 KB, trace.js 8.25 KB, wire.js 0.93 KB
page: base + router (base page + @solidjs/router: createRouter, two routes, preload, useNavigate) 41.55 KB +7 B (+0.0%) +46 B −5 B 41.56 KB ✅ assets.js 0.78 KB, bind.js 1.85 KB, decode.js 6.23 KB, lazy-page.js 0.04 KB, regions.js 0.80 KB, server.js 1.02 KB, serverForms.js 3.58 KB, trace.js 8.28 KB, wire.js 0.94 KB
page: live + router (live page + @solidjs/router: createRouter, two routes, preload, useNavigate) 47.32 KB +42 B (+0.1%) +47 B −7 B 47.30 KB ⚠️ over by 18 B, 27 B minified headroom eager (counted): client.js 27.88 KB; assets.js 0.78 KB, bind.js 1.85 KB, decode.js 6.23 KB, lazy-page.js 0.04 KB, regions.js 0.81 KB, server.js 1.02 KB, serverForms.js 3.30 KB, trace.js 8.23 KB, wire.js 0.94 KB
server: floor (getRequestEvent + isServer) 1.33 KB 0 B 0 B 0 B 1.34 KB ✅
server: renderToString (the server-render floor) 20.40 KB 0 B 0 B +4 B 20.42 KB ✅

⚠️ Over the brotli cap within the minified allowance (passes)

  • app: compiled hydrating (the same JSX todo app through hydrate(), compiled hydratable): over brotli cap by 2 B; minified 99,681 B vs 99,672 B recorded with the cap (+9 B) — 11 B of the 20 B minified allowance left; +0 B minified over this PR's base
  • page: compiled live server components (the compiled base page + live/GET + action + isPending/latest): over brotli cap by 49 B; minified 123,609 B vs 123,628 B recorded with the cap (−19 B) — 39 B of the 20 B minified allowance left; +45 B minified over this PR's base
  • page: live + router (live page + @solidjs/router: createRouter, two routes, preload, useNavigate): over brotli cap by 18 B; minified 143,151 B vs 143,158 B recorded with the cap (−7 B) — 27 B of the 20 B minified allowance left; +47 B minified over this PR's base

Bundled with Rolldown (what Vite ships), brotli q11, decimal KB. A scenario fails only when it is over its brotli cap and its minified size is more than 20 B over the minified recorded with the cap; over the cap within that allowance is brotli layout noise and passes with a warning. Caps and their recorded minified in scripts/size/scenarios.js; the floor and page caps in floor-caps.json are frozen (lower only, or Size-Exception: in the PR body). npm run ratchet lowers caps per RC; it never raises one (scripts/size/README.md).

@coveralls

coveralls commented Oct 8, 2026 •

Copy link
Copy Markdown

Coverage Report for CI Build 37876195412

Coverage remained the same at 76.014%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 1312
Covered Lines: 1061
Line Coverage: 80.87%
Relevant Branches: 1031
Covered Branches: 720
Branch Coverage: 69.84%
Branches in Coverage %: Yes
Coverage Strength: 26.91 hits per line

💛 - Coveralls

@codspeed

codspeed Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 195 untouched benchmarks


Comparing fix-3889 (e699955) with next (4afc662)

Open in CodSpeed

ryansolid and others added 4 commits October 8, 2026 16:05
… frame index (#3889)

Listing every mount and grouping it with data-fn grew the frames client by about 140 B brotli. A later site keeps its own data-fid (id~n) and the lookup probes that id, so hydration keys stay independent without a second index.

Co-authored-by: Cursor <cursoragent@cursor.com>
CI measured the eager frames client at 11,406 B brotli. The cap matches
that size, rounded up to 0.01 KB.

Co-authored-by: Cursor <cursoragent@cursor.com>
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