An island is an interactive Preact component inside an otherwise static page. Neutron emits the island runtime and component chunk without hydrating the surrounding route.

Add an island

import { Island } from "@neutron-build/core/client";
import Counter from "../components/Counter";

export default function Page() {
  return (
    <main>
      <h1>Static content</h1>
      <Island component={Counter} client="visible" initialCount={0} />
    </main>
  );
}

Props are serialized into the generated HTML, so pass values that can be represented as JSON.

Hydration timing

| Value | Hydration begins | | --- | --- | | client="load" | When the island runtime starts | | client="visible" | When the element enters the viewport | | client="idle" | During browser idle time, with a timeout fallback | | client="media" | When the query passed through media matches | | client="only" | Immediately, rendering a fresh tree if no server markup exists |

For a media-controlled island:

<Island
  component={MobileMenu}
  client="media"
  media="(max-width: 720px)"
/>

Prerendered markup is not reconciled

Hydration attaches listeners to the markup the build produced; it does not diff attributes against the first client render. Whatever an island rendered at build time sticks until state changes it, and a static build has no URL, no localStorage, and no viewport. Anything derived from those must start unresolved on the server and on the first client render alike, then fill in after mount:

function Nav() {
  // "" at build time and on the first client render; the real path after mount.
  const [path, setPath] = useState("");
  useEffect(() => setPath(location.pathname), []);
  return <a class={path === "/docs" ? "active" : ""} href="/docs">Docs</a>;
}

Reading location.pathname in the initial state keeps the build-time value on every page (the same prerendered active class everywhere). This does not show up in neutron dev, where nothing is prerendered, so check a production build.

Use app mode instead when navigation state, loaders, and actions belong to the whole route rather than a few isolated controls.