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

  1. Refresh a few times: outer ticks live, cached receivedAt stays frozen (within the 60s TTL).
  2. Click the button: action runs server-side, revalidateTag("demo") drops the cached entry and reports how many entries were removed.
  3. The action's loader rerun fetches fresh — the next receivedAt you 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>
        );
    }