Revalidation (ISR)
Revalidation caches rendered pages and refreshes them in the background. When a page goes stale, visitors still see the cached copy while the Worker renders a fresh one. This is often called Incremental Static Regeneration (ISR).
On this pageCache a page and set when it goes stale · Refresh pages on demand · Understand cache behaviour
Use revalidation with the SDK SSR, Pages mode, or a supported SSR framework. For framework apps, the SDK caches public HTML documents. It does not cache framework data requests.
To turn off ISR app-wide but keep your per-page settings, set "routing": { "isr": false } in void.config.ts. This overrides per-page revalidate and prerender policies. Set isr to true to restore them. Build-time HTML from output: "static" is not affected.
Turn on revalidation
This step turns on revalidation. Add routing.revalidate to void.config.ts with a TTL in seconds.
{
"routing": {
"revalidate": 60
}
}revalidate: 0 turns revalidation off. This equals plain SSR.
Per-path revalidation
This step sets a TTL per URL pattern with an object.
{
"routing": {
"revalidate": {
"/": 60,
"/docs/*": 31536000,
"/user/*": 0,
"*": 30
}
}
}Rules match in order. The first match wins. The * glob matches any characters including /.
Special values:
31536000(1 year): cached until the next deploy, because deploys clear the ISR cache0: never cached. The worker always renders it, like plain SSR
Per-page revalidation (Pages mode)
This step overrides the TTL for one page in Pages mode. Export revalidate from its .server.ts file.
// pages/products/index.server.ts
export const revalidate = 120; // cache for 2 minutes
export const loader = defineHandler(async (c) => {
// ...
});Per-page values win over void.config.ts patterns.
A page's revalidate export also applies to routes/ handlers whose URLs match the page pattern. For example, if pages/[...slug].server.ts exports revalidate, the SDK can also cache the GET response of routes/now.ts. The SDK never caches paths under /api. To keep a route handler's response out of the cache, set x-revalidate: 0 on it. See Per-response TTL override.
Precedence
If several sources set a TTL, the most specific wins:
x-revalidateresponse header (per-response).server.tsexport (per-page, Pages mode only)void.config.tsrouting.revalidatepath pattern matchvoid.config.tsrouting.revalidateglobal number or"*"fallback
How revalidation works
The first request renders and caches the page. While it is fresh, visitors get it at once. Once stale, the SDK still serves it and renders a new version in the background.
A request without a cached entry waits for the page to render.
Per-response TTL override
Route handlers can set the x-revalidate header to override the TTL for one response:
export const GET = defineHandler(async (c) => {
// Don't cache this particular response
c.header('x-revalidate', '0');
return c.html('...');
});x-revalidate: 0: skip caching for this responsex-revalidate: 300: cache for 5 minutes instead of the default
Only complete 200 responses are cached. A response with Cache-Control: private, no-store, or no-cache, or any Set-Cookie header, stays private to that request. If a public page starts returning one of those headers, the SDK removes its cached response.
Responses that vary by request headers, such as Vary: Accept-Language or Vary: Origin, render live. Vary: * also turns off shared caching. The SDK's X-VoidPages header is supported, because HTML and Pages JSON are cached separately.
Cache bypass
Requests with Cookie, Authorization or Cloudflare Access's Cf-Access-Jwt-Assertion header skip the ISR cache. They always go to the worker. Authenticated responses are never shared between users, even when Access sends no cookie.
Cache keys and rewrites
For a dispatch rewrite, the cache key holds both the destination and the original path. So a direct /en/docs/foo and a rewritten /docs/foo use different entries.
Query parameters are ignored by default. To cache separate responses per parameter, add it to routing.revalidateQueryAllowlist. Requests with other parameters get the cached page. If nothing is cached, they render live and are not stored. Add every parameter a page reads to the allowlist. Middleware c.rewrite() runs after the ISR lookup, so it adds no cache variant.
Purge the destination pathname with revalidate({ paths }). Purging /en/docs/foo clears both direct and rewritten variants. Purging only /docs/foo does not clear them. Entries are stored under the destination.
On-demand revalidation
This step purges ISR cache entries from a route handler.
import { revalidate } from 'void/isr';
export const POST = defineHandler(async (c) => {
// ... update content in database ...
// Purge specific pages
await revalidate({ paths: ['/', '/blog/hello-world'] });
// Or purge all cached pages
await revalidate({ all: true });
return c.json({ ok: true });
});