Page caching and Cloudflare

Compose OPTZ response caching with scoped Cloudflare purge and soft invalidation.

On this page

OPTZ already has static files in memory, ISR, tagged server-function caching and an opt-in response cache. The CDN integration extends those primitives; it does not add a second page cache or a new browser runtime.

Existing cache policy

Use a route's cache policy for intentional public dynamic/API responses, revalidate for ISR pages, and revalidatePath / revalidateTag after mutations. Cookie/Authorization requests, personal header reads, Set-Cookie and private/no-store responses bypass shared caching. Do not try to make personalized responses public by adding a tag.

server.cdnMaxAge adds CDN freshness and page tags. Match your Cloudflare Cache Rules to these headers; do not install a blanket cache-everything rule for authenticated APIs or action routes.

Revalidation hook

import { defineConfig } from "optz";
import { cloudflarePurge } from "optz/cdn";

export default defineConfig({
  site: { url: process.env.PUBLIC_URL! },
  server: { cdnMaxAge: 300 },
  revalidation: {
    purge: cloudflarePurge({
      zoneId: process.env.CF_ZONE_ID!,
      token: process.env.CF_API_TOKEN!,
      mode: "purge",
      onError: () => console.error("CDN invalidation needs retry"),
    }),
  },
});

Default tag mode combines explicit tags with optz-path:<path> tags. Layout/prefix changes purge a validated hostname/path prefix. byTag: false supports local path-to-URL purging, but cannot safely expand a tag-only change to URLs: it reports that error instead of silently purging the entire zone. Relative/foreign-origin path tricks are rejected.

The hook reports failures without failing a completed application mutation. For delivery guarantees, enqueue a retry in your own durable worker; a response hook is not a durable queue.

Explicit purge and soft invalidation

import { purgeCloudflare } from "optz/cdn";

const options = {
  zoneId: process.env.CF_ZONE_ID!,
  token: process.env.CF_API_TOKEN!,
};
await purgeCloudflare(options, { files: ["https://example.com/docs"] });
await purgeCloudflare(options, { tags: ["docs"] });
await purgeCloudflare(options, { prefixes: ["example.com/docs/"] });
await purgeCloudflare(options, { hosts: ["example.com"] });
// Broad removal is explicit, never a fallback after an error.
await purgeCloudflare(options, { purge_everything: true });

// Keep content stale for conditional revalidation instead of deleting it.
await purgeCloudflare({ ...options, mode: "invalidate" }, { tags: ["docs"] });

Exactly one target kind is accepted. URL targets reject credentials/fragments; prefix targets use hostname/path without a scheme or query. Targets are deduplicated and sent in batches of at most 100. Requests have a five-second default timeout (configurable 1–60,000 ms), reject redirects and require both a successful HTTP status and success: true in the API response. Errors do not echo response bodies or credentials. Empty targets do no network work.

No hidden retry storm follows a 429. Limits vary by Cloudflare plan and operation; schedule backoff centrally when needed. Use a token scoped to the intended zone's Cache Purge permission. Keep it server-only, for example in Doppler.

Purge removes content. Soft invalidation marks it stale, and the next request can reuse the cached object after a 304 or replace it after a full origin response. A successful API result means the request was accepted, not that an object was previously cached. Confirm the desired CF-Cache-Status behavior against your configured zone.

Primary references: purge API, availability and limits, soft invalidation.