Documentation
Caching on Cloudflare
Choose how vinext stores rendered responses and cached data on Cloudflare Workers.
Caching
Vinext supports several caching setups on Cloudflare. Caching is optional: if you do not enable it, your app still works without a shared response or data cache.
What Vinext caches
There are two main kinds of cache:
- Response / CDN cache: rendered HTML, RSC payloads, and other ISR responses.
- Data cache: cached
fetchcalls,unstable_cache, and functions marked with"use cache".
revalidatePath() and revalidateTag() invalidate entries in the configured cache. A route that uses request-specific data such as cookies or headers is not added to the shared response cache.
Static files and browser caching are separate from these options. Cloudflare can cache built assets without enabling a vinext cache adapter.
Opting a route out of response caching
If an App Router page or Route Handler must render for every request and should not consult the shared response cache, prefer an explicit route segment config:
export const dynamic = "force-dynamic";
This matches Next.js behavior and lets vinext identify the route at build time, before a configured Response Store or Workers Cache lookup. Use it for an unconditional opt-out instead of relying on request APIs such as cookies() or headers() to make the route dynamic during rendering. A matching public cache policy from next.config still takes precedence when one is deliberately configured.
Options
| Setup | Response storage | Data storage | Best for | Main trade-off |
|---|---|---|---|---|
| No persistent cache | In-memory | In-memory | Dynamic apps and initial migrations | Every request may need to render and fetch its data |
| Workers Response Store (recommended) | Workers Cache backed by R2 | Workers Response Store | Durable responses, SWR, cache warming, and one cache system for responses and data | Requires R2, a SQLite Durable Object, and either a separate cache Worker or extra bindings on the app Worker |
| Workers Cache + data cache | Workers Cache | Workers KV | Fast edge responses using Cloudflare's native caches | Cached responses have no durable backing store, and a hit in one region does not guarantee a hit elsewhere |
| Data cache | Workers KV | Workers KV | A simple persistent cache without Workers Cache | Requests still reach the Worker and KV is eventually consistent |
When caching is enabled through vinext init, Workers Response Store is the default choice.
Workers Response Store
Workers Response Store is the most complete and best supported option. Workers Cache serves hot responses, R2 provides a durable backing store, and a SQLite Durable Object tracks metadata and invalidation state. An edge miss can read the stored response from R2 instead of immediately rendering the route again.
It also handles the data cache, so it replaces both cdnAdapter() and kvDataAdapter():
import { responseStoreAdapter } from "@vinext/cloudflare/cache/response-store-adapter";
vinext({ cache: responseStoreAdapter() });
Metadata sharding is available as an explicit scaling option:
vinext({ cache: responseStoreAdapter({ shards: 16 }) });
Each cache key remains strongly coordinated by one SQLite Durable Object. Tag/path refreshes and purges fan out across all shards. The option is disabled by default, and changing the count starts a new cache layout for the deployed Worker version.
The default service-binding mode keeps the cache service in a separate Worker. vinext init creates wrangler.response-store.jsonc alongside the application config and adds a deployment script:
pnpm run deploy:response-store
Then deploy the application using its normal deploy command. The Response Store only needs redeploying when its package or Wrangler configuration changes. New application versions can be deployed normally.
For a single-Worker deployment, you can also consider using self-contained mode:
vinext({ cache: responseStoreAdapter({ mode: "self-contained" }) });
This avoids a second Worker, but the application Worker owns the R2 bucket, Durable Object, and cache-enabled entrypoint, which may not be desired for some applications.
Workers Cache and KV
The older split setup uses Workers Cache for rendered responses and Workers KV for cached data:
import { cdnAdapter } from "@vinext/cloudflare/cache/cdn-adapter";
import { kvDataAdapter } from "@vinext/cloudflare/cache/kv-data-adapter";
vinext({
cache: {
cdn: cdnAdapter(),
data: kvDataAdapter(),
},
});
Workers Cache can serve a response without rerunning the render stage. Middleware and request routing still run before vinext selects the cached response entrypoint.
This setup is fast when an entry is present at the edge, but Workers Cache is distributed rather than one globally shared cache. A response cached in one region may still miss in another. Tiered caching can reduce this duplication, but without a durable response store a regional miss or eviction can require another render. The KV data cache is also eventually consistent.
KV data cache
You can use Workers KV without Workers Cache:
import { kvDataAdapter } from "@vinext/cloudflare/cache/kv-data-adapter";
vinext({
cache: {
data: kvDataAdapter(),
},
});
Add the matching namespace to wrangler.jsonc:
{
"kv_namespaces": [{ "binding": "VINEXT_KV_CACHE", "id": "<your-namespace-id>" }],
}
This is the smallest persistent setup. The same data cache can hold ISR responses and nested cached data, but every lookup goes through the application Worker and KV's eventual consistency may briefly expose older values after an update.
Cache warming
Cache warming is optional during Cloudflare deploys. With a configured cache adapter, run:
npx @vinext/cloudflare deploy --experimental-warm-cdn-cache
For an existing deployment serving 100% of traffic from one version, vinext uploads the new Worker, stages it at 0%, discovers routes through the staged Worker with its real bindings, checks readiness, and requests eligible responses to fill the configured cache before promoting the version. Unlike a local prerender and KV bulk upload, rendering happens in the deployed Worker. Static exports (output: "export") still need local prerendered files.
The default warmup makes one fill request per admitted cache identity. Add --warm-cdn-certify to require a second, header-only request that proves warmed entries are reusable before promotion. If staging or required warmup fails, the command stops rather than promoting an unverified version; inspect the deployment state before retrying. A first deployment cannot use this staged flow: deploy normally once, then enable warming on subsequent deploys. The command also needs a reachable production route or custom domain and a Worker name for staged version overrides; use --warm-cdn-target https://example.com if the target origin cannot be inferred.
Only discoverable, cacheable route identities are warmed; dynamic parameters and query variants are not all enumerated automatically. HTML, RSC, and Pages Router data use separate requests where applicable. For Workers Cache, a warm request does not guarantee a cache hit in every region. See Cloudflare's Workers Caching documentation.
Which option should I choose?
- Choose no persistent cache while migrating an app or when every response is intentionally dynamic.
- Choose Workers Response Store (recommended) for the most complete Cloudflare caching setup and durable response storage.
- Choose Workers Cache + KV when you specifically want the existing native Workers Cache architecture and accept that responses have no backing store.
- Choose KV only when you want the simplest persistent cache and do not need Workers Cache to serve responses.
You can run vinext init --platform=cloudflare to configure these choices. The generated Vite and Wrangler files are normal source files and can be adjusted later.
Freshness and revalidation
vinext follows Next.js-style cache semantics:
- A fresh entry is returned immediately.
- A stale entry may be returned while stale-while-revalidate refreshes it in the background.
- An expired entry is not returned and must be regenerated.
revalidatePath()invalidates content associated with a path.revalidateTag()invalidates content associated with a cache tag.
The adapter changes where entries live and how they are served, but it should not change the caching API used by application code.