revalidateTag (fetch-cache invalidation)
Server-side fetch(url, { next: { tags:
["demo"] } }) caches the upstream response and
indexes it by tag. Clicking the button POSTs to a server
action that calls revalidateTag("demo") — every
entry tagged "demo" is dropped from
FetchCache; the next page render re-fetches
fresh from upstream.
Output
Outer (live): 2026-05-03T10:37:03.967Z
Cached receivedAt: 2026-05-03T10:37:03.968Z
How to read this
-
Refresh a few times: outer ticks live, cached
receivedAtstays frozen (within the 60s TTL). -
Click the button: action runs server-side,
revalidateTag("demo")drops the cached entry and reports how many entries were removed. -
The action's loader rerun fetches fresh — the next
receivedAtyou see is the post-invalidation timestamp.
Source
import { revalidateTag } from "@bext-stack/framework/cache";
export const dynamic = "force-dynamic";
export async function action({ request }: { request: Request }) {
const removed = revalidateTag("demo"); // returns count dropped
return { ok: true, removed };
}
export default async function Page({ actionData }: any) {
// Cached upstream fetch — receivedAt stays frozen until the tag
// is invalidated.
const r = await fetch("http://127.0.0.1:3032/api/echo?key=revalidate-tag", {
next: { revalidate: 60, tags: ["demo"] },
});
const data = await r.json();
return (
<div>
<p>Outer (live): {new Date().toISOString()}</p>
<p>Cached receivedAt: <strong>{data.receivedAt}</strong></p>
<form method="post">
<button type="submit">revalidateTag("demo")</button>
</form>
{actionData?.ok ? <p>Invalidated {actionData.removed} entries</p> : null}
</div>
);
}