Suspense boundary

Each <Suspense> boundary emits its fallback immediately, then streams the resolved real content via an out-of-order <template> + __bextSuspense.swap(id) pair when the children settle. Open the network tab and watch the trickle — the fallbacks land in the first chunk, the real content lands as each promise resolves.

Tip
Importing <Suspense> from @bext-stack/framework/streaming automatically activates streaming mode for the route — no need to declare renderingMode manually. Each boundary is independent: a slow promise doesn't block the others.

Output (fallback first, real content swaps in)

  • loading fast…
  • loading medium…
  • loading slow…
src/app/examples/suspense/page.tsxTSX
import { Suspense } from "@bext-stack/framework/streaming";

// Site default is ISR — opt out so each request streams fresh.
export const dynamic = "force-dynamic";

async function slowQuery(label: string, ms: number) {
  await new Promise(r => setTimeout(r, ms));
  return <li>{label} ready ({ms}ms)</li>;
}

export default function Page() {
  return (
    <ul>
      <Suspense fallback={<li>loading fast…</li>}>{slowQuery("fast", 100)}</Suspense>
      <Suspense fallback={<li>loading med…</li>}>{slowQuery("med", 300)}</Suspense>
      <Suspense fallback={<li>loading slow…</li>}>{slowQuery("slow", 600)}</Suspense>
    </ul>
  );
}