diff --git a/packages/solid/src/server/signals.ts b/packages/solid/src/server/signals.ts index ae203f7e6..72bd12056 100644 --- a/packages/solid/src/server/signals.ts +++ b/packages/solid/src/server/signals.ts @@ -935,7 +935,14 @@ function serverEffect( } effectFn?.((ssrSource ? (comp.value ?? result) : result) as any, undefined); } catch (err) { - // Swallow errors from effects on server + // NotReadyError is suspense control flow — must keep propagating so the + // surrounding Loading boundary can react. For real errors, record on the + // computation and re-throw so a wrapping `createErrorBoundary` / + // `` can catch instead of the error vanishing into the void + // (#2777). + if (err instanceof NotReadyError) throw err; + comp.error = err; + throw err; } } diff --git a/packages/solid/test/server/signals.spec.ts b/packages/solid/test/server/signals.spec.ts index 758ce35e2..4d3a883cb 100644 --- a/packages/solid/test/server/signals.spec.ts +++ b/packages/solid/test/server/signals.spec.ts @@ -417,6 +417,27 @@ describe("Server createErrorBoundary", () => { { id: "test" } ); }); + + test("catches errors thrown from createEffect on the server (#2777)", () => { + createRoot( + () => { + const result = createErrorBoundary( + () => { + createEffect( + () => { + throw new Error("server effect boom"); + }, + () => {} + ); + return "children"; + }, + (err: () => unknown) => `caught: ${(err() as Error).message}` + ); + expect(result()).toBe("caught: server effect boom"); + }, + { id: "test" } + ); + }); }); // === createLoadingBoundary ===