Skip to content

[2.0 rc.19 / next] A hydrated <Errored> showing the server's fallback claims DOM for content behind a context provider (Hydration key miss) #4348

Closed

Description

@ibniss

Describe the bug

Since rc.19, hydrating an <Errored> that the server rendered as its fallback logs Hydration key miss when the boundary's content renders through a context provider. The same page hydrates without warnings on rc.18. We hit it with a headless dropdown menu, whose trigger renders inside a context provider.

import { createContext, createMemo, Errored, Loading } from "solid-js";

const Ctx = createContext();

function Content() {
  const data = createMemo(async () => {
    await Promise.resolve();
    throw new Error("server read failed");
  });
  return (
    <Ctx value={1}>
      <button>
        <span>{data().name}</span>
      </button>
    </Ctx>
  );
}

export const App = () => (
  <main>
    <Loading fallback={<p>loading</p>}>
      <Errored fallback={null}>
        <Content />
      </Errored>
    </Loading>
  </main>
);
Hydration key miss for "10000100": no server-rendered element carries this key (template: <button><span>). A detached element was created instead; its subtree will not appear in the document or become interactive. …

Without the <Ctx> provider, the same tree hydrates cleanly on rc.19.

Your Example Website or App

Inline above: solid-js and @solidjs/web only.

Steps to Reproduce the Bug or Issue

  1. Server: render <App /> with renderToStream, plus generateHydrationScript() in the head (dev build, hydratable: true).
  2. Client: hydrate(() => <App />, root) (dev build).
  3. Open the page and the console.
  4. See the key miss for the <button> template.

Expected behavior

The boundary keeps the server's fallback and nothing under it tries to claim server DOM, as on rc.18 and as for the same content without the provider.

Platform

  • OS: Linux
  • Browser: Chromium 1243 (Playwright 1.63)
  • Versions: solid-js 2.0.0-rc.19, @solidjs/web 2.0.0-rc.19, Node 24.21.0. hydratedCreateErrorBoundary on next (56a982c) is unchanged from rc.19.

Additional context

When it started. We think this comes from the fix for #4273 in rc.19 ("An <Errored> boundary hydrated in the error state the server rendered now recovers"): the client now re-runs the content of an <Errored> that the server rendered as its fallback, so it can recover.

What we think happens. hydratedCreateErrorBoundary runs the content inside hydrateWindow("", fn, undefined, ~clock), so nothing claims while fn() is on the stack. As far as we can tell, createBoundary then flattens the content in a separate computed (computed(() => flatten(read(c)))) after that window has closed. A context provider resolves its children lazily, so the provider's children are first created during that flatten, with sharedConfig.hydrating still true and no claim override in place. Their templates then look for server nodes that the fallback replaced. We may be missing something about how the window is meant to extend to later work.

Possible directions. We're not sure which fits the design best:

  • Resolve the content inside the window, so lazily created children exist before it closes (what we do locally, below).
  • Make "claim nothing" a property of the content's owner subtree rather than of the synchronous window, so anything created under that owner later still doesn't claim.
  • Run the boundary's flatten under the same claim override as the content.

Workaround we're carrying. We patch hydratedCreateErrorBoundary in the dist builds to flatten the content once, untracked, inside the window:

 () => _hydratingValue
-  ? hydrateWindow("", fn, undefined, (o = getOwner(), ~clock))
+  ? hydrateWindow("", () => {
+      const content = fn();
+      try {
+        untrack(() => flatten(content));
+      } catch {}
+      return content;
+    }, undefined, (o = getOwner(), ~clock))
   : fn()

With it the warnings go away, the server's fallback stays when nothing changes, and the boundary still recovers when its input changes on the client. We haven't checked other lazy shapes beyond context providers, and resolving the content eagerly may have costs we haven't seen.

Activity

  1. added 2 commits that reference this issue on Oct 28, 2026
  2. ryansolid commented on Oct 28, 2026

    @ryansolid
    Member

    Fixed on next by #4357 (c7f4403); it ships in the next RC.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions