Island
Wrap a component as a client-hydrated island using the Island JSX wrapper.
The <Island> wrapper
Import Island from "@takazudo/zfb" and wrap any component that should hydrate in the browser. Everything else stays server-rendered HTML.
import { Island } from "@takazudo/zfb";
import Counter from "./Counter";
export default function Page() {
return (
<main>
<h1>My Page</h1>
<Island when="visible">
<Counter />
</Island>
</main>
);
}
At SSR time the <Island> wrapper emits a <div data-zfb-island="Counter" data-when="visible"> marker with the server-rendered child inside. The client runtime (@takazudo/zfb-runtime) queries these markers and mounts the component when the when condition fires.
IslandProps
interface IslandProps {
when?: When;
ssrFallback?: VNode;
children?: VNode;
}
when— hydration strategy. Defaults to"load". See Hydration strategies below.ssrFallback— enables SSR-skip mode (equivalent to Astro’sclient:only). When provided, the heavychildrenare not evaluated server-side;ssrFallbackis rendered in their place and the client swaps in the real component on hydration.children— the component to hydrate.
Hydration strategies
The when prop accepts three values, all shipped today:
| Value | Behaviour |
|---|---|
"load" | Hydrate immediately when the page’s JavaScript runs. This is the default when when is omitted. |
"visible" | Hydrate when the island’s root element enters the viewport (IntersectionObserver, threshold 0). Cheapest deferral for off-screen content. |
"idle" | Hydrate during the browser’s next idle callback. Falls back to setTimeout(0) on platforms without requestIdleCallback. |
SSR-skip mode
Pass ssrFallback to skip server rendering the heavy child entirely:
import { Island } from "@takazudo/zfb";
import HeavyChart from "./HeavyChart";
export default function Page() {
return (
<Island ssrFallback={<div>Loading chart…</div>}>
<HeavyChart data={data} />
</Island>
);
}
The server emits <div data-zfb-island-skip-ssr="HeavyChart" data-when="load">…fallback…</div>. On hydration the client runtime renders HeavyChart into the placeholder.
Exported constants and helpers
The following are exported from "@takazudo/zfb" alongside Island:
HYDRATE_MARKER_ATTR— thedata-zfb-islandattribute name.SKIP_SSR_MARKER_ATTR— thedata-zfb-island-skip-ssrattribute name.ANONYMOUS_COMPONENT_NAME— fallback name used when a wrapped child’s identity cannot be determined.resolveWhen(when: unknown): When— validates and normalises awhenvalue. Unknown inputs fall back to"load"(with a warning in development).
“use client” directive
zfb also supports a "use client" file-level directive as an alternative authoring style. A component file whose first statement is the literal string "use client" is treated as an island entry by the build scanner (crates/zfb-islands). The esbuild-backed bundler compiles it to ESM separately, with its own dependency graph.
"use client";
import { useState } from "preact/hooks";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
Import the island from a server component or page module. The build pipeline wires the hydration bootstrap automatically.
For the broader story on islands architecture and when to reach for one, see Islands.