Skip to content

Conversation

dennev
Copy link
Contributor

@dennev dennev commented Aug 12, 2025

Summary

Objective: To make SuspenseList usable in SSR and add some simple tests.

Currently, SuspenseList is a fairly reliable implementation of the component in CSR, but it doesn't work well in SSR.
This modification aims to improve the reliability of SuspenseList by aligning the hydration key with the client-side.

How did you test this change?

In addition to the issue with SuspenseList and direct DOM elements injected, it also fails to follow the specification in CSR.
For example, it currently fails to drive in the following situations:

  1. Pages with a deferred initial element
// src/pages/Page2.tsx and Page3
export default function Page2() {
  return <div>Page2</div>;
}
// src/pages/Page1.tsx
import { createSignal, createResource, Show } from "solid-js";
import { isServer } from "solid-js/web";

export default function Page1() {
  // 1. Create a signal to act as a trigger.
  const [refetchTrigger, setRefetchTrigger] = createSignal(0);
  const sleep = ms => {
    return new Promise(resolve => setTimeout(resolve, ms));
  };
  // 2. Use the signal as the source for createResource.
  // The resource will refetch data whenever this signal changes.
  const [data] = createResource(refetchTrigger, async () => {
    const response = await sleep(5000);
    return "resolved";
  });

  // 3. Start an infinite loop when the component mounts.
  // Use 'isServer' to prevent this from running on the server.
  // if (!isServer) {
  //   setInterval(() => {
  //     setRefetchTrigger(prev => prev + 1);
  //   }, 1000); // Trigger the resource every second by changing the signal value.
  // }

  return (
    <div>
      <h1>Page1 - Infinite Loop</h1>
      <Show when={data()} fallback={<div>Loading...</div>}>
        <p>Data from server: {data()}</p>
      </Show>
    </div>
  );
}
// src/Home.tsx
import { Suspense, SuspenseList, createResource } from "solid-js";
import Page1 from "~/pages/Page1";
import Page3 from "~/pages/Page3";
import Page2 from "~/pages/Page2";

export default function Home() {
  const [data] = createResource(() => ["red", "blue", "green"]);
  return (

    <SuspenseList revealOrder="backwards">
      <Suspense>
        <Page1 />
      </Suspense>
    </SuspenseList>
  );
}
// Error Message
Hydration Mismatch. Unable to find DOM nodes for hydration key: 000000001000002000000100001 <div><h1>Page1 - Infinite Loop</h1><!--$--><!--/--></div>
  1. Nested Suspense/SuspenseList
// src/main.tsx
import { Suspense, SuspenseList, createResource } from "solid-js";
import Page1 from "~/pages/Page1";
import Page3 from "~/pages/Page3";
import Page2 from "~/pages/Page2";

export default function Home() {
  const [data] = createResource(() => ["red", "blue", "green"]);
  return (

    <SuspenseList revealOrder="backwards">
      {data()}
      <div>{data()}</div>
      <Suspense>
        <Page2 />
        <Suspense>
          <div>{data()}</div>
          <Page3 />
          <SuspenseList revealOrder="backwards">
            <Suspense>
              <Page2 />
              <Suspense>
                <Page3 />
              </Suspense>
              <SuspenseList revealOrder="backwards">
                <Suspense>
                  {data()}
                  <Page2 />
                  <Suspense>
                    <Page3 />
                  </Suspense>
                </Suspense>
                <div>{data()}</div>
                <Suspense>
                  <Page1 />
                </Suspense>
              </SuspenseList>
              {data()}
            </Suspense>
            <Suspense>
              <Page1 />
            </Suspense>
          </SuspenseList>
        </Suspense>
        <div>{data()}</div>
      </Suspense>
      <Suspense>
        <Page1 />
      </Suspense>
    </SuspenseList>
  );
}
// Error Message
Hydration Mismatch. Unable to find DOM nodes for hydration key: 000000001000002000000100000 <div>Page2</div>
  1. SuspenseList and direct DOM elements injected - [Bug?]: SuspenseList hydration error #2124

This commit is an incomplete solution because it fails to express revealOrder. However, the existing component does not perform deferred initial loading at all, so revealOrder itself is never expressed.

So I'm requesting a pull because I don't think this commit breaks existing functionality, and I think it should be prioritized in terms of making the component more stable to run.

Copy link

changeset-bot bot commented Aug 12, 2025

🦋 Changeset detected

Latest commit: 5f6310d

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

This PR includes changesets to release 2 packages
Name Type
solid-js Patch
test-integration 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

@ryansolid
Copy link
Member

Thanks.. I haven't really tried SuspenseList in SSR projects for ages so yeah it's quite likely we're off by an ID. Yeah reviewing looks that way as SuspenseList calls createComponent on Provider in the client. Thanks for looking into this.

@coveralls
Copy link

Pull Request Test Coverage Report for Build 16908319277

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 0 of 0 changed or added relevant lines in 0 files are covered.
  • 1 unchanged line in 1 file lost coverage.
  • Overall coverage decreased (-0.06%) to 85.188%

Files with Coverage Reduction New Missed Lines %
packages/solid/src/reactive/scheduler.ts 1 81.08%
Totals Coverage Status
Change from base Build 16791160984: -0.06%
Covered Lines: 2334
Relevant Lines: 2669

💛 - Coveralls

@ryansolid ryansolid merged commit 62c5a98 into solidjs:main Aug 12, 2025
1 check passed
@dennev dennev deleted the fix/ssr-SuspenseList branch August 13, 2025 00:19
@dennev dennev mentioned this pull request Aug 13, 2025
2 tasks
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.

[Bug?]: SuspenseList hydration error

3 participants