Server Islands

The page shell is rendered once and ISR-cached; the personalized fragment is rendered separately by a force-dynamic route and swapped in client-side. So one dynamic block no longer forces the whole route to force-dynamic — the shell stays on the cache hot path and only the hole is fetched per request.

Tip
Without server islands, a single personalized block (logged-in menu, cart, an A/B test) forces force-dynamic on the whole page → no ISR caching at all. Here the cached shell holds a placeholder; the inline loader fetches the /api/server-islands-frag fragment per request. Refresh and the fragment's timestamp + token change every time, while the surrounding shell stays served from cache.

Output

loading…

The fragment below is loaded by a tiny inline script after the shell renders. Refresh and it changes every time; the page around it stays cached.

src/app/examples/server-islands/page.tsxTSX
import { Raw, ServerIsland } from "@bext-stack/framework";

// The page shell is ISR-cached (the demo site renders mode = "isr").
// Only the island below is dynamic — fetched per request.
export default function Page() {
  return (
    <div>
      <h1>My (cached) page shell</h1>
      <Raw
        html={ServerIsland({
          src: "/api/server-islands-frag",
          fallback: "<em>loading…</em>",
          on: "load", // or "visible" | "idle"
        })}
      />
    </div>
  );
}
src/app/api/server-islands-frag/route.tsTypeScript
// src/app/api/server-islands-frag/route.ts
// The dynamic hole. MUST live under /api/ — a page.tsx shadows a
// nested-sibling route.ts, so the fragment gets its own segment.
export const dynamic = "force-dynamic";

export function GET() {
  const now = new Date().toISOString();
  const n = Math.floor(Math.random() * 100000);
  return new Response(
    "<strong>Personalized server island</strong> — rendered " + now +
      ", token " + n + ".",
    {
      headers: {
        "content-type": "text/html; charset=utf-8",
        "cache-control": "no-store",
      },
    },
  );
}