Islands
Brix renders Preact pages on the server and hydrates only components marked as islands, keeping static page content outside the browser interaction boundary.
Create an island
Add a directive to a PascalCase component module and export exactly one named PascalCase runtime component:
"use island";
import { useSignal } from "brix/hooks";
export function QuantityPicker() {
const quantity = useSignal(1);
return (
<button type="button" onClick={() => quantity.value++}>
Quantity: {quantity}
</button>
);
}
Type-only exports are allowed. Default exports and unrelated runtime exports fail discovery and build validation. Import component hooks from brix/hooks, not directly from Preact or the signals package.
Render it from a page
An ordinary server-rendered component can include the island normally:
import { QuantityPicker } from "#/features/cart/QuantityPicker.tsx";
export function ProductPage({ name }: { name: string }) {
return (
<main>
<h1>{name}</h1>
<QuantityPicker />
</main>
);
}
Brix server-renders the complete HTML, serializes supported island props, and hydrates the marked boundary in the browser.
Choose hydration timing
The directive controls scheduling:
"use island"hydrates immediately"use island:idle"schedules hydration after immediate work, yielding between modules"use island:lazy"hydrates each boundary as it enters the viewport
Immediate modules retain encounter order and run in short, time-bounded batches. Brix checks its budget between modules, yielding through scheduler.yield() or a timer fallback rather than waiting for an idle callback. A single large island or repeated-instance module is not interrupted mid-hydration.
Idle work uses scheduler.yield() when available, then an idle callback or timer fallback. Keep important controls immediate and explicitly defer nonessential independent islands; Brix does not automatically reorder them by viewport or business priority.
For example, a below-the-fold control can defer hydration:
"use island:lazy";
export function ReviewFilters() {
return <button type="button">Filter reviews</button>;
}
Choose timing from actual interaction needs. Content that never becomes interactive should remain an ordinary component.
Understand nested islands
When one island renders another island normally, the outer island owns that interactive subtree. Brix emits and hydrates only the outer boundary; the inner directive does not create a second hydration boundary.
An island passed through server-owned JSX slot content remains independent. Prefer straightforward composition and avoid wrappers created only to force another boundary.
Keep boundaries safe
Island modules are browser code. Do not import #brix/server, brix/server, environment secrets, databases, or other server-only modules. Move data loading into a page route or call a typed API route.
Props cross a serialization boundary. Pass data rather than request contexts, services, or functions. Brix's generated client and island manifest own hydration startup; application code does not call init manually.
Verify interaction
Use browser component tests for event handling and route harness tests for server rendering:
brix test --project browser
brix test --project node
brix build
See testing for the distinction between browser tests, route tests, and HTTP contract mocks.