Client Islands
Hydrate interactive components in the browser using React (react-dom/client). Pages are static SSR HTML; islands are the only interactive units. Each island …
Purpose
Hydrate interactive components in the browser using React (react-dom/client). Pages are static SSR HTML; islands are the only interactive units. Each island is its own independent React root — there is no whole-page React tree.
Public surface
export function createClient(): {
register(name: string, component: ComponentType): void
hydrateAll(): void
}
The synthetic client entry produced by 04-build imports every island and calls register(name, Component), then hydrateAll().
Behavior
hydrateAll()queries[data-island]markers in the document.- For each marker, read
data-island="<name>"and the adjacent<script type="application/json" data-props>blob. - Look up the registered component by name and call
hydrateRoot(marker, <Component {...props} />)fromreact-dom/client. Each marker gets its own root — nevercreateRoot, which would discard the SSR markup. - Errors during hydration log to
console.errorwith the island name; the rest of the page stays alive.
Constraints
- Islands use only
react+react-dom/client. No global React tree — each island is an independenthydrateRootcall. - Islands must be self-contained — no shared module-level state across islands. Cross-island coordination is a Phase 4 RFC.
- Props must be JSON-serializable. If a component needs a function prop, expose a route handler instead. Non-JSON types (Date, Map, Set, BigInt) require an explicit (de)serializer at the island boundary.
Bundle-size note
React + react-dom/client is ~45KB gzipped. Bun.build code-splitting hoists the React runtime into a single shared chunk so multiple islands on one page do not duplicate it. Pages with zero islands ship zero client JS (the synthetic entry short-circuits before importing React).
Non-goals
- Partial hydration triggered by viewport, idle, or interaction (Phase 4 RFC).
- Cross-island state stores.
Acceptance criteria
- A counter island marker hydrates with
hydrateRootanduseStateworks in the browser without a hydration-mismatch warning. - A page with zero islands ships zero client JS (no React runtime in the bundle).
- A page with multiple islands shares a single React runtime chunk.
- A misspelled
data-islandname logs an error but does not throw out of the page.