Islands: zero client JS by default

The feature

June pages are server-rendered and ship zero client JavaScript — this site's pages, including the one you're reading, send none. Interactivity is opt-in per subtree:

// app/page.tsx — server component (the default export IS the view)
import { Island } from "@junejs/core/islands";

export default function Page() {
  return (
    <main>
      <h1>Mostly static</h1>
      <Island name="Counter" props={{ start: 0 }} />  {/* the ONE live subtree */}
    </main>
  );
}
// app/_client.ts — the explicit registry; its presence is what enables /client.js
import { hydrateIslands } from "@junejs/core/islands-client";
import { Counter } from "./Counter";

hydrateIslands({ Counter });

<Island> server-renders a marker carrying the registry name + JSON props; hydrateIslands() scans the page and hydrateRoot()s each marker. No app/_client.* file → no bundle, no script tag, output unchanged.

Try it

npm create june my-app    # the starter ships a working Counter island

The starter's counter — and the framework's own e2e test — run the REAL production bundle a june build ships against the built worker's SSR markup: what's tested is what deploys.

Why it matters

Hover-prerendering and View Transitions already make navigation feel instant with no client code (Navigation). Islands keep the budget at zero until a specific subtree earns it.