Composition (route × Suspense × ISR × fetch-cache)

Every cache layer stacking on one page. The outer timestamp ticks per request (force-dynamic). Three Suspense boundaries stream their real content as it settles; each wraps an <ISR> fragment with a different TTL. The slowest fragment additionally fetches with next.revalidate, so layered caching keeps even the outer-miss render cheap.

Tip
Cache layers don't cancel each other out: a 30s ISR miss can still find the fetch result in the FetchCache (TTL 8s), saving the upstream round-trip. Add a tag (tags: ["composition"]) to selectively invalidate the FetchCache via /__bext/sdk/cache/purge-tag without touching the ISR fragments.

Output (refresh repeatedly to watch the cadences)

Outer (live): 2026-06-15T17:50:49.339Z

  • loading fast…
  • loading medium…
  • loading slow…

Cadence map

LayerCadenceCache
Outerevery request
Fast fragment~5sFragmentCache (timed)
Medium fragment~15sFragmentCache (timed)
Slow fragment~30sFragmentCache (timed)
Slow's upstream fetch~8s (only on slow miss)FetchCache + tag "composition"
src/app/examples/composition/page.tsxTSX
import { Suspense } from "@bext-stack/framework/streaming";
import { ISR } from "@bext-stack/framework/cache";

export const dynamic = "force-dynamic";

export default function Page() {
  const outer = new Date().toISOString();
  return (
    <div>
      <p>Outer (live): {outer}</p>
      <Suspense fallback={<p>loading fast…</p>}>
        <ISR cacheKey="composition:fast" ttl={5}>
          {async () => {
            await new Promise(r => setTimeout(r, 200));
            return `<p>Fast (5s ISR): ${new Date().toISOString()}</p>`;
          }}
        </ISR>
      </Suspense>
      <Suspense fallback={<p>loading medium…</p>}>
        <ISR cacheKey="composition:medium" ttl={15}>
          {async () => {
            await new Promise(r => setTimeout(r, 400));
            return `<p>Medium (15s ISR): ${new Date().toISOString()}</p>`;
          }}
        </ISR>
      </Suspense>
      <Suspense fallback={<p>loading slow…</p>}>
        <ISR cacheKey="composition:slow" ttl={30}>
          {async () => {
            await new Promise(r => setTimeout(r, 800));
            // fetch with its own 8s next-cache layer.
            const r = await fetch("https://demo.bext.dev/api/echo?key=composition", {
              next: { revalidate: 8, tags: ["composition"] },
            });
            const data = await r.json();
            return `<p>Slow (30s ISR + 8s fetch-cache): ${data.receivedAt}</p>`;
          }}
        </ISR>
      </Suspense>
    </div>
  );
}