Weftv0.23.1
GitHub

Errors and Server Rendering

The final step. We can now use services and async; here we handle what happens when async work fails, and how the same tree renders on the server.

Error boundaries

A component's failures accumulate on its E channel. Wrap a subtree in a Boundary.* variant to intercept them and render a fallback instead of failing the mount:

typescript
import { Boundary, h } from "@weftui/core";
import { Data, Effect } from "effect";

class ApiError extends Data.TaggedError("ApiError")<{ status: number }> {}

const SafeWidget = () =>
  Boundary.catchAll({ fallback: (e) => h.div({ class: "error" }, `Request failed: ${e.status}`) }, [
    Effect.fail(new ApiError({ status: 503 })),
  ]);

There are six failure-catch variants — catchAll, catchAllCause, catchTag, catchTags, catchSome, catchIf — mirroring Effect's own error operators. A failure that a boundary does not match re-raises to the nearest enclosing boundary; if none catches it, the mount fails. The conceptual model (and why the boundary's type reflects exactly which failures are handled) is Boundaries and Suspense.

Render on the server

The same component tree renders to HTML on the server and hydrates in place on the client — no re-render, no flash. The server produces markup (plus inline data), and hydrate adopts that existing DOM and resumes reactivity:

typescript
// server entry
import { renderToStringHydratable } from "@weftui/dom/server";
import { Effect } from "effect";
import { App } from "./app";

export const render = () => Effect.runPromise(renderToStringHydratable(App()));
typescript
// client entry
import { hydrate } from "@weftui/dom/client";
import { Effect } from "effect";
import { App } from "./app";

void Effect.runPromise(hydrate(App(), document.getElementById("root")!));

The same side-effect-free App is imported by both entries. For server-resolved data that replays into the client without a second request, Boundary.rpc extends this model — resolve an rpc on the server, serialize its result into the HTML, replay it on hydrate, then keep the region live for refetch.

You're done

You have built up every core idea: components and h, reactive state and streams, services and async, boundaries and SSR. Where to go next depends on what you are doing: