You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[2.0 rc.10–rc.13] A live source created outside a streamed <Loading> takes over when the shell finishes hydrating, before the boundary's fragment hydrates: unclaimed server DOM + duplicate render, or undefined rows in a keyed <For> #3764
Setup:@solidjs/vite-plugin start mode, ssr: true, production build + vite preview (Node). Reproduces on 2.0.0-rc.10 and 2.0.0-rc.13 (solid-js, @solidjs/web, @solidjs/signals, compiler all at rc.13, vite-plugin next.47), 5/5 runs each. No router, no Cloudflare/workerd needed.
The server answer resolves after 300 ms, so the boundary streams. The browser value is an async iterable branded Symbol.for('solid.LiveSource'); it subscribes inside the first next()'s executor.
Expected: the source is adopted from the serialized snapshot when its boundary hydrates, then taken over once. One copy of the DOM, no errors.
Actual: the live source subscribes at ~30 ms (when the shell finishes hydrating); the boundary's fragment arrives at ~300 ms.
A: the server <ul _hk=…> stays and a second <ul> is client-rendered. In dev: Hydration completed with 1 unclaimed server-rendered node(s).
B:TypeError: Cannot read properties of undefined (reading 'id') from the keyed function; the server list stays beside the error fallback.
With ssrSource: 'hybrid', A is fixed (takeover waits for the boundary), but B still throws, at takeover time.
If the live answer arrives before the fragment and differs from the snapshot, the late snapshot overwrites the newer live value.
Dev reproduces too once the server delay exceeds the dev entry's load time (e.g. 4000 ms) — production builds hit it with ordinary delays because the entry loads fast.
A source created inside the boundary (its owner is the boundary) behaves correctly: it waits for the boundary to hydrate before subscribing.
Cause (as far as I can read):hydratedCreateRoot → markTopLevelSnapshotScope → openLiveScope(root). Setting sharedConfig.hydrating = false calls releaseLiveScope(root), which releases every root-scoped takeover gate armed by armLiveTakeover while boundaries still waiting on streamed fragments (_pendingBoundaries > 0) read those sources.
Workarounds we use:ssrSource: 'client' for such sources, or create the source inside the streamed boundary; ssrSource: 'hybrid' works for memos but not for stores read by a derived memo.
Happy to share the full minimal Vite app if useful.
Fixed by #3817: a live source whose first value is still streaming now takes over at hydration end, so a pending boundary no longer claims against the live replacement. Restoring per-scope takeover for that case is tracked in #3819.
Activity
test(web): type the solidjs#3764 harness store row
fix(solid): a loadingValue live node keeps its per-scope takeover (so…
docs(stage8): D8 amendment — in-flight values take over at hydration …
docs(room): note the solidjs#3764 exception to the shell's per-scope …
fix(solid): order the solidjs#3764 takeover condition to fit the size…
fix(solid): a live node with a streaming server value takes over at h…
ryansolid commentedon Oct 6, 2026