Fetch cache (next.revalidate)

Server-side fetch(url, { next: { revalidate: 10, tags: ["echo-demo"] } }) caches the upstream response for 10 seconds. The outer timestamp ticks on every refresh; the inner receivedAt from the cached fetch stays frozen for the freshness window.

Tip
The fetch cache only stores text/JSON responses — binary bodies and streams are never cached. Also note that <code>revalidateTag</code> invalidation is process-local today: if multiple workers serve your site, each has its own cache and must be invalidated independently.

Output

Outer (live): 2026-09-06T04:03:32.759Z

Cached receivedAt: 2026-09-06T04:03:32.775Z

Full response JSON
{"method":"GET","path":"/api/echo","query":{"key":"fetch-cache-demo"},"headers":{"user-agent":"ureq/2.12.1","content-type":null},"body":null,"receivedAt":"2026-09-06T04:03:32.775Z"}

How it works

The fetch polyfill in the V8 host forwards options.next through to __httpFetch, which consults bext_core::cache::fetch_cache::FetchCache before touching the network. On a hit, the cached JSON envelope is returned directly. On a miss, the upstream call runs and the response is stored under hash(url, method, body), indexed by every tag in next.tags.

revalidateTag(tag) from @bext-stack/framework/cache calls __bextRevalidateTag on the host, which walks the tag index and drops every entry tagged with that string. Cross-worker fan-out via the existing PURGE wire frame is a follow-up; today the invalidation is process-local.

src/app/examples/fetch-cache/page.tsxTSX
import { revalidateTag } from "@bext-stack/framework/cache";

// Fetch with cache config — response is stored for 10 s,
// indexed under the tag "echo-demo" for targeted invalidation.
const r = await fetch("/api/echo?key=demo", {
  next: { revalidate: 10, tags: ["echo-demo"] },
});
const data = await r.json();

// Elsewhere — drop every cached entry tagged "echo-demo".
revalidateTag("echo-demo");