Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

`lazy()`: remounting an already-loaded component renders empty (or the Suspense fallback) since 1.9.14

Open Beginner friendly
#4,118 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@itisrmk is already working on this.

Since Oct 17, 2026.

  • #1 by @itisrmk — closed without merging
  • #4135 by @itisrmk — open

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
62/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Domain
frontend

Research direction

The bug is in the client branch of lazy() in the solid-js source (the dist/solid.js build points to it), where an onCleanup clears the shared comp cache when any instance is disposed. Read that branch and the load() path that sets comp after resolution, then check whether any existing test covers retrying a failed import. Done when a remount after a successful load renders synchronously, a remount after a failed import still retries, and the existing tests pass.

Written by the indexing model from the issue text.

Description

Describe the bug

Since 1.9.14, disposing any instance of a lazy() component clears the module-level component cache shared by all instances. The next mount of the same, already-loaded component creates a new resource and renders "" until it resolves. Inside <Suspense>, the fallback shows again. In 1.9.13 the remount renders the component synchronously.

This shows up as a visible flash whenever a lazily loaded page or overlay is closed and opened again.

Your Example Website or App

https://playground.solidjs.com/anonymous/08d6df4c-9789-4c96-8910-bb818f3fb391

Steps to Reproduce the Bug or Issue

Run test with solid version 1.9.17 from playground url. If you test it locally with solid 1.9.13 works as expected

import { createRoot, lazy, Suspense } from "solid-js";
import { render } from "solid-js/web";
import { createSignal } from "solid-js";

const flush = () => new Promise<void>((r) => setTimeout(r, 0));

function App() {
  const [lines, setLines] = createSignal<{ text: string; pass: boolean }[]>([]);

  async function runTest() {
    const out: { text: string; pass: boolean }[] = [];
    const check = (label: string, actual: unknown) => {
      const pass = actual === "loaded";
      out.push({ text: `${label}: ${JSON.stringify(actual)}`, pass });
    };

    // ── Case 1: without Suspense ──────────────────────────────
    const Lazy1 = lazy(() => Promise.resolve({ default: () => "loaded" }));
    let first1 = createRoot((d) => ({ d, v: Lazy1({}) }));
    await flush();
    first1.d();
    let second1 = createRoot((d) => ({ d, v: Lazy1({}) }));
    const r1 =
      typeof second1.v === "function" ? (second1.v as any)() : second1.v;
    check("without Suspense – remount", r1);
    second1.d();

    // ── Case 2: with Suspense ─────────────────────────────────
    const Lazy2 = lazy(() => Promise.resolve({ default: () => "loaded" }));
    const mkView = () =>
      Suspense({
        fallback: "fallback",
        get children() {
          return Lazy2({});
        },
      });
    let first2 = createRoot((d) => ({ d, v: mkView() }));
    await flush();
    first2.d();
    let second2 = createRoot((d) => ({ d, v: mkView() }));
    const r2 =
      typeof second2.v === "function" ? (second2.v as any)() : second2.v;
    check("with Suspense    – remount", r2);
    second2.d();

    setLines(out);
  }

  return (
    <div style="font-family:monospace;padding:1rem">
      <h2>lazy() remount regression · solid-js 1.9.14–1.9.17</h2>
      <p>
        Expected: <code>"loaded"</code> both times (solid-js 1.9.13 ✅)
      </p>
      <button onClick={runTest}>▶ Run test</button>
      <ul>
        {lines().map((l) => (
          <li style={`color:${l.pass ? "green" : "red"}`}>
            {l.pass ? "✅" : "❌"} {l.text}
          </li>
        ))}
      </ul>
    </div>
  );
}

render(() => <App />, document.getElementById("app")!);

Expected behavior

Once the module has loaded, a remount renders the component synchronously, as in 1.9.13:

== solid-js 1.9.13
without Suspense  first mount after load: "loaded", remount: "loaded"
with Suspense     first mount after load: "loaded", remount: "loaded"

Actual, in 1.9.14 through 1.9.17:

== solid-js 1.9.14
without Suspense  first mount after load: "loaded", remount: ""
with Suspense     first mount after load: "loaded", remount: "fallback"
== solid-js 1.9.17
without Suspense  first mount after load: "loaded", remount: ""
with Suspense     first mount after load: "loaded", remount: "fallback"
Cause

1.9.14 added an onCleanup in the client branch of lazy() (dist/solid.js):

     } else if (!comp) {
       const [s] = createResource(() => (p || (p = fn())).then(mod => mod.default));
       comp = s;
+      onCleanup(() => comp = undefined);
     }

comp is shared by every instance of the lazy() component, but the cleanup is registered on the owner of whichever instance created the resource. Disposing that one instance therefore clears the cache for all of them, even after the module has loaded. The next mount takes the !comp branch again and waits for a new resource. The module promise p is still cached, so nothing is re-downloaded, but the new resource resolves asynchronously, which causes the empty render or Suspense fallback.

Possible fix

If the cleanup is meant to let a failed import be retried on the next mount, it could clear the cache only when it still points at this instance's resource and that resource hasn't loaded. For example, against 1.9.17, where load() already sets comp = () => mod.default once the module resolves:

-      onCleanup(() => comp = undefined);
+      onCleanup(() => comp === s && (comp = undefined));

With this change, a remount after a successful load renders synchronously, and a remount after a failed import still retries.

Screenshots or Videos

No response

Platform
  • OS: [macOS]
  • solid-js: 1.9.14 to 1.9.17 (works in 1.9.13)
  • Client build (dist/solid.cjs / dist/solid.js), no SSR or hydration
  • Node 22 for the reproduction; we first saw it in a SolidJS app using the universal renderer
Additional context

No response

Dominant language
TypeScript
Stars
36.1k
Forks
1.1k
Avg merge
11h 45m
Merged PRs (30d)
329

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from solidjs/solid

All issues in solidjs/solid

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.