fix: dev overlay corrupting first navigation after hydration - #2312
Draft
brenelz wants to merge 1 commit into
Draft
fix: dev overlay corrupting first navigation after hydration#2312brenelz wants to merge 1 commit into
brenelz wants to merge 1 commit into
Conversation
The dev toolbar's <Portal> hands the hydrating tree a client-created marker text node that hydration never inserts into the DOM. The phantom entry in insert()'s bookkeeping corrupts the first reconcile after a navigation, leaving the previous page's DOM orphaned on screen (two pages visible at once). Render the toolbar in its own root outside the app tree instead, mounted after DOMContentLoaded because rendering while the SSR stream is still open steals hydration keys from pending suspense chunks. Also pre-bundle @jridgewell/trace-mapping for the client so the error viewer's lazy import chain stops rejecting on raw CJS/UMD files that the dep scanner can never discover. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: afa5502 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 |
✅ Deploy Preview for solid-start-landing-page ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
commit: |
Member
|
seems to me that this is a start of another issue (hydration + portal interaction). I'll do some tests first before I make a conclusion. |
brenelz
marked this pull request as draft
August 25, 2026 02:45
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
With the dev overlay enabled (the default), the first client-side navigation after any SSR page load in dev leaves the previous page's DOM on screen — both routes render at once. Easy to hit as: home → about → back → refresh → click a link.
devOverlay: falsemakes it disappear, which is how it was tracked down.Root cause
DevToolbarrenders a<Portal>inside the hydrating tree. On the client,Portalreturns a freshly created empty text node as its placeholder. In a normal client render the parentinsert()puts that marker into the DOM — but during hydration DOM operations are skipped (nodes are claimed in place), so the marker never enters the document (container._$hostisnull) while still being recorded ininsert()'s bookkeeping. The first navigation re-flattens the root children and reconciles against an array containing a phantom node that isn't in the DOM; the positional diff goes wrong and the outgoing page's nodes are orphaned instead of removed.Two non-fixes, discovered the hard way:
onMountrenders it in hydration claim mode (effects flush whilesharedConfig.contextis still set) and steals hydration keys from streaming suspense chunks →Hydration Mismatchon the streamed content.sharedConfig.contextdoesn't help either: it's unset between stream chunks, and any reflow of the hydrated root mid-stream still breaks the pending chunks' claiming.The fix
The toolbar now renders in its own
render()root appended todocument.body, so it never participates in hydration at all — mounted afterDOMContentLoaded(the document stays inloadingreadyState for the whole SSR stream, so that's the reliable stream-end signal) plus one macrotask. Reactive wiring (error signal, server-fn store, drag handling) is unchanged since the closures cross the root boundary fine.Also includes a second dev-overlay fix: the lazily-imported error viewer reaches
@jridgewell/trace-mappingonly through@solidjs/startitself, so Vite's dep scanner never discovers it and serves its CJS/UMD deps raw — every dev page load loggedUncaught (in promise) SyntaxError: ... does not provide an export named 'default'(@jridgewell/resolve-uri), and the error viewer failed to load exactly when needed. The client environment now pre-bundles it viaoptimizeDeps.include: ["@solidjs/start > @jridgewell/trace-mapping"](root-leveloptimizeDepsis ignored with the Vite 8 environments API; it has to be per-environment).Verification
Against a minimal reproduction (streaming SSR, shared layout, 60ms
createAsyncin a root provider), all previously failing cases now pass with the toolbar fully functional (buttons, panels, error viewer, drag):The underlying
<Portal>-under-hydration phantom-marker behavior looks like a solid-js/dom-expressions core issue worth an upstream fix; this PR removes the dev overlay's exposure to it.🤖 Generated with Claude Code