Skip to main content

KV Cache Handler

The KVCacheHandler provides persistent ISR caching on Cloudflare Workers using KV as the storage backend. It supports time-based expiry (stale-while-revalidate) and tag-based invalidation.

Installation

The KV cache handler is included with vinext:

Quick Start

1. Create KV Namespace

Create a KV namespace via the Cloudflare dashboard or CLI:
This outputs a namespace ID:

2. Configure wrangler.jsonc

Add the KV namespace to your worker config:

3. Set Cache Handler in Worker

4. Use ISR in Your Pages

API Reference

Constructor

Parameters

kvNamespace (required)
Type: KVNamespace
Cloudflare KV namespace binding.
options
Type: { appPrefix?: string }
Optional configuration.
options.appPrefix
Type: string
Prefix for all cache keys. Useful for multi-tenant or multi-app deployments sharing one KV namespace.
Keys are stored as myapp:cache:key.

Methods

get

Fetch a cache entry. Returns null on miss or invalidation. Returns:
  • cacheState: 'stale' - Entry expired but still returned (stale-while-revalidate)
  • cacheState: 'fresh' or undefined - Entry is fresh

set

Store a cache entry with optional tags and revalidation time. Context Options:
Example:

revalidateTag

Invalidate all entries with the specified tag(s). Example:

resetRequestCache

No-op. KV is stateless per request.

Cache Behavior

Stale-While-Revalidate

When a cache entry expires (revalidate time passes):
  1. The stale entry is returned immediately (cacheState: 'stale')
  2. Background revalidation is triggered
  3. Next request receives the fresh entry
This ensures zero-latency cache hits even during revalidation.

Tag-Based Invalidation

Entries can be tagged for grouped invalidation:
Invalidate from a Server Action:

TTL Management

KV entries have two expiry concepts:
  1. Revalidate time - When entry becomes “stale” (but still served)
  2. KV TTL - When entry is physically deleted from KV
The KV TTL is automatically set to 10x the revalidate time (capped at 30 days) to ensure stale entries remain available during revalidation. Example:
  • revalidate: 60 → KV TTL: 600 seconds (10 minutes)
  • revalidate: 3600 → KV TTL: 36000 seconds (10 hours)
  • revalidate: 86400 → KV TTL: 864000 seconds (10 days)

Entry Structure

Cache entries are stored as JSON:
  • value - The cached data (page HTML, RSC payload, fetch response, etc.)
  • tags - Array of cache tags for invalidation
  • lastModified - Timestamp when entry was created
  • revalidateAt - Absolute timestamp (ms) when entry becomes stale

Key Prefixes

The handler uses two key prefixes:
  • cache: - Cache entries (cache:key)
  • __tag: - Tag invalidation timestamps (__tag:posts)
With an appPrefix, keys become {appPrefix}:cache:key and {appPrefix}:__tag:posts.

Tag Validation

Cache tags are validated for safety:
  • Max length: 256 characters
  • Allowed: alphanumeric, -, _, .
  • Disallowed: control characters, path separators (/, \), colons (:)
Invalid tags are silently ignored.

Supported Cache Types

The KV cache handler supports all Next.js cache entry types:
  • FETCH - fetch() responses
  • APP_PAGE - App Router page renders (RSC payload + HTML)
  • PAGES - Pages Router page renders
  • APP_ROUTE - Route handler responses
  • REDIRECT - Redirect metadata
  • IMAGE - Optimized images

ArrayBuffer Serialization

Some cache types contain ArrayBuffer fields (RSC payloads, route handler bodies, images). These are automatically:
  1. Converted to base64 before storage
  2. Restored to ArrayBuffer on retrieval
Corrupted base64 is treated as a cache miss.

Multi-Tenant Deployments

Use appPrefix to isolate cache entries per app:
Now app1.example.com and app2.example.com have isolated caches in the same KV namespace.

Performance Considerations

KV Consistency

KV is eventually consistent. After a set() or revalidateTag(), it may take a few seconds for the change to propagate globally. For strong consistency requirements, use Durable Objects instead of KV.

Tag Lookup Parallelization

Tag checks are parallelized for low latency:
An entry with 10 tags incurs 1 KV read (the entry) + 10 parallel KV reads (the tags) = ~11 KV reads total.

KV Limits

  • Read latency: ~10-50ms globally
  • Write latency: ~100ms-1s (eventual consistency)
  • Key size limit: 512 bytes
  • Value size limit: 25 MB
  • Operations per day: Unlimited on paid plan, 100k/day on free plan
See Cloudflare KV limits.

Example: Full Worker Setup