Islands Mistakes
Generated from
.agents/rules/anti-patterns.md(the same source as MCPget_anti_patterns). Each entry is a real mistake + its fix; where a detector code is listed, the linter /pyreon doctor/ MCPvalidatecatches it automatically.
Importing island from the @pyreon/server main barrel into client/route code
Import from @pyreon/server/client in any file that ships to the client (every @pyreon/zero route does).
The main barrel also exports
createHandler/prerender, which pullnode:*modules and a secondregisterSingleton. The SSG build then throws[Pyreon] Duplicate @pyreon/server detected, and a dual@pyreon/corecrashes hydrated islands withCannot read properties of undefined (reading 'ref').packages/core/server/src/client.tsre-exportsisland,IslandOptions,IslandMeta;scripts/check-client-bundle-node-imports.tsgates@pyreon/server/client.Any package with a server barrel should re-export its client-safe pieces from a client-safe subentry.
Island hydration that depends on the host's render or a one-shot scan
An island owns its hydration.
On the client,
island()(packages/core/server/src/island.ts) renders only the<pyreon-island>marker (no children, so a hydrating host adopts the marker and never descends), thenonMountloads the chunk and mounts perdata-hydrateviascheduleHydration/schedulePrefetch.Do not render the async component inline (a host mount cannot await it; the route error boundary swallows it) or rely on a one-shot
hydrateIslandsAutoscan (it runs before lazy routes mount their markers).Test:
e2e/zero-islands.spec.ts.
Deferred island hydration without the marker's context owner
Context is owner-based, and a late hydrateRoot has no parent owner, so useContext() cannot reach ancestor providers (rocketstyle crashes reading the theme: Cannot read properties of undefined (reading 'base')).
Capture
getContextOwner()synchronously in the marker's render body (not inonMount), thread it throughscheduleHydration, and mount withrunWithContextOwner(owner, () => hydrateRoot(...))(client.ts:hydrateIsland). Static-islands apps passnull.Applies to any primitive that defers a mount past the render frame. Test:
server/src/tests/island-client.test.tsx.
Registering a hydrate: 'never' island in hydrateIslands({ ... })
The loader never runs, but the import still pulls the component into the client bundle. Drop the entry.
hydrateIslandsAuto(registry)(frompyreon({ islands: true }), the default) already omits never-islands; the detector targets manual registries.Detected when the
island()andhydrateIslandscall share a file (compiler/src/pyreon-intercept.ts:detectIslandNeverWithRegistry); cross-file cases belong topyreon doctor --check-islands.
Detected by: island-never-with-registry-entry — surfaced by @pyreon/lint / pyreon doctor / MCP validate.
hydrate: 'interaction' for above-the-fold interactive components
Use 'load' for anything the user interacts with on first paint.
'interaction'suits interactive content that is not visible at first paint (modals, dropdowns, command palettes).Only clicks and form submits are replayed after hydration; focus, pointerenter and touchstart trigger hydration without replay.
Pairing prefetch: 'idle' | 'visible' with hydrate: 'load' or 'never'
Prefetch is a no-op there and data-prefetch is silently dropped. Drop the field. The canonical pairing is hydrate: 'visible' + prefetch: 'idle'.
Passing children to an island
Island props are JSON-serialized into data-props; children, functions, symbols and undefined are stripped. Render content from serializable props (for example an id the island fetches with), or move the island wrapper inside the component.
Duplicate name across island() declarations
The registry is keyed by name, so only the first loader fires and the second island silently never hydrates. Use distinct names. pyreon doctor --check-islands reports duplicate-name.
Nested islands
An island whose loader target also declares an island() is unsupported (the outer hydrateRoot replaces the inner subtree first). Flatten them. pyreon doctor --check-islands reports nested-island (compiler/src/island-audit.ts:detectNestedIsland).
Dead islands
An island() in a file nothing imports never renders. Route it or delete it. Auto-registry () => import('PATH') counts as an import. pyreon doctor --check-islands reports dead-island.
Cross-file registry drift
A manual hydrateIslands({ X }) key with no matching island() name (keys are case-sensitive). Use hydrateIslandsAuto() with pyreon({ islands: true }) to avoid manual sync. pyreon doctor --check-islands reports registry-mismatch.