Component Islands & Isolated Hydration
Relevant source files
The following files were used as context for generating this wiki page:
Farm.js implements a component-island protocol that enables isolated client hydration within server-rendered layouts. This system allows server-rendered layouts to remain server-only modules while individual interactive "use client" components hydrate independently with their own React roots and serialized props.
Sources: rfcs/0001-isolated-client-hydration.md:12-16, ARCHITECTURE.md:46-49
Core Architecture
The architecture separates the application into static server-rendered regions and interactive client islands. Instead of promoting an entire route to a browser hydration root, Farm.js uses static analysis to identify supported "use client" leaves and wraps them in boundary markers.
Hydration Strategy Classification
Static analysis classifies client edges into different hydration plans based on the module shape.
| Module Shape | Hydration Plan |
|---|---|
Layout contains "use client" | Existing route-wide layout hydration |
Layout exports hydrate = true | Existing route-wide layout hydration |
| Server layout imports supported client leaves | Isolated component boundaries |
| Server layout uses unsupported client composition | Existing route-wide fallback |
| Server layout has no client edge | No hydration |
Sources: rfcs/0001-isolated-client-hydration.md:86-101, packages/farm/src/tests/client-component.test.ts:241-255
Data Flow Diagram
The following diagram illustrates how the server prepares a page with isolated boundaries for the client runtime.
Isolated Boundaries
Isolated boundaries use a specific HTML protocol to communicate between the server and client. The server emits a <farm-client-boundary> element containing the SSR markup and a sibling <script type="application/json"> containing the serialized props.
Boundary Protocol
The marker element serves as the container for hydrateRoot(). Each boundary receives a unique ID to associate its container with its specific prop record.
<farm-client-boundary
data-farm-client-boundary="b1"
data-farm-client-ref="account-menu:AccountMenu"
data-farm-island-strategy="load"
style="display: contents"
>
<button type="button">Account</button>
</farm-client-boundary>
<script type="application/json" data-farm-client-props="b1">
{ "avatarUrl": "/avatar.png" }
</script>
Sources: rfcs/0001-isolated-client-hydration.md:112-126, packages/farm/src/tests/isolated-boundary.test.tsx:43-58
Serialization Constraints
The system only serializes plain, JSON-compatible data. It rejects functions, symbols, React elements, and class instances. If an unsupported value is detected, the system falls back to route-wide hydration or leaves the boundary inert with a diagnostic warning.
Sources: rfcs/0001-isolated-client-hydration.md:173-181, packages/farm/src/tests/isolated-boundary.test.tsx:317-328
Island Runtime
The client-side island-runtime manages the lifecycle of component islands, including discovery, scheduling, and hydration.
Hydration Scheduling
Farm.js supports multiple island strategies that control when a component module is loaded and hydrated.
- load: Hydrates immediately on page load.
- interaction: Defers hydration until the user interacts with the component area.
- visible: Defers hydration until the component enters the viewport.
- idle: Hydrates when the browser is idle.
Sources: packages/farm/src/client/island-runtime.ts, rfcs/0001-isolated-client-hydration.md:104-110, packages/farm/src/tests/client-component.test.ts:646-665
Runtime Flow
The following sequence diagram shows the interaction between the runtime and the browser during hydration.
Performance and Resource Guarding
To prevent overhead from too many independent React roots, Farm.js implements a cost model. If a route exceeds the measured limit (currently 4 isolated roots), the system automatically falls back to route-wide hydration.
Cost Guard Logic
- Static Limit: A client graph creating more than 4 isolated roots triggers a fallback.
- Data Dependency: Boundary lists that are data-dependent (e.g., generated via
.map()) trigger a fallback because their total cost cannot be proven before streaming begins. - Parser Sensitivity: Markers rendering inside
<table>,<select>, or<svg>trigger route-wide hydration to avoid parser-induced relocation.
Sources: rfcs/0001-isolated-client-hydration.md:255-263, packages/farm/src/tests/client-component.test.ts:311-335, packages/farm/src/tests/client-component.test.ts:420-438
Build System Integration
The universal-build.ts process generates a boundary manifest and configures Nitro to handle these islands in production.
Manifest Generation
The build process converts module paths to chunk references and generates loaders for the client runtime.
const farmIsolatedBoundaryLoaders = {
"module-id-1": () => import("./chunks/boundary-1.js"),
"module-id-2": () => import("./chunks/boundary-2.js"),
};
Sources: packages/farm/src/nitro/universal-build.ts:1535-1544, rfcs/0001-isolated-client-hydration.md:130-140
Conclusion
The Component Islands system provides a performance optimization for React applications by keeping large layouts out of the client bundle. By using isolated roots, Farm.js preserves client state during SPA navigation while minimizing the amount of JavaScript required for initial hydration. The system prioritizes correctness by falling back to traditional SSR when complex compositions or unsupported data types are encountered.
Sources: rfcs/0001-isolated-client-hydration.md:204-216, ARCHITECTURE.md:118-121