Cache Handler
Rebask routes server cache operations through a CacheStorage implementation. In the default Node
runtime, the production handler persists entries and tag invalidations on disk and adds a
per-process in-memory LRU. Multiple server instances each keep their own filesystem cache.
Use cacheHandler to connect Rebask to shared storage such as Redis. Platform adapters may provide their own handler and route supported entries through platform-provided storage.
Configure a handler
The cacheHandler option accepts a CacheStorage implementation, which defines the contract
between Rebask and the storage backend for reading, writing, deleting, and invalidating server cache
entries:
import type { RebaskConfig } from "@rebask/core"
import { CustomCacheHandler } from "./custom-handler"
export default {
cacheHandler: new CustomCacheHandler(),
} satisfies RebaskConfigFor most backends, extend BaseCacheStorage. It handles entry expiration and staleness, so you only
implement what touches your store. Only reach for CacheStorage directly when you need to override
that behavior entirely.
Server caching goes through one CacheStorage implementation: the default filesystem handler, your
cacheHandler, or one provided by a platform adapter. Treat the keys you receive as opaque and
store each entry unchanged.
BaseCacheStorage
BaseCacheStorage supplies get and getEntryStatus to handle entry expiration and staleness.
You can extend it and implement only the storage backend and your tag strategy:
import { BaseCacheStorage, type CacheEntry } from "@rebask/core/cache"
export class RedisCacheStorage extends BaseCacheStorage {
protected async read(key: string) {
const data = await redis.get(key)
return data ? (JSON.parse(data) as CacheEntry) : null
}
async set(key: string, entry: CacheEntry) {
await redis.set(key, JSON.stringify(entry))
}
async delete(key: string) {
await redis.del(key)
}
// Lazy tag invalidation.
async revalidateTag(tag: string) {
await redis.set(`invalidated:${tag}`, String(Date.now()))
}
protected async invalidatedAt(tag: string): Promise<number | undefined> {
const value = await redis.get(`invalidated:${tag}`)
return value ? Number(value) : undefined
}
}The base get wraps your read: it removes expired entries and, if you override invalidatedAt,
entries whose tags were invalidated. Store tag invalidations in your backend so they survive
restarts and are shared across instances.
There are two common ways to handle tags:
- Lazy — hold tag invalidation timestamps separately. Override
invalidatedAtto answer when a tag was last invalidated, and record it inrevalidateTag. - Eager — delete the tagged entries in
revalidateTag, and leaveinvalidatedAtas its default.
CacheStorage
CacheStorage is the contract between Rebask and your storage backend. Its methods cover entry
access, expiration, tag invalidation, and optional coordination of background revalidation:
import type { CacheEntry, CacheEntryStatus, CacheStorage } from "@rebask/core/cache"
interface CacheStorage {
get(key: string): Promise<CacheEntry | null>
set(key: string, entry: CacheEntry, options?: { durable?: boolean }): Promise<void>
delete(key: string): Promise<void>
revalidateTag(tag: string): Promise<void>
getEntryStatus(entry: CacheEntry): CacheEntryStatus
tryClaim?(key: string): Promise<(() => void | Promise<void>) | null>
}get
get(key: string): Promise<CacheEntry | null>Returns the entry for key, or null when it is missing or expired.
Stale entries must still be returned. Rebask can serve a stale entry while it refreshes the value in the background. An expired entry must never be returned.
Rebask wraps every configured handler with an expiry check using getEntryStatus. If a handler
returns an expired entry, Rebask deletes it and treats the lookup as a miss.
set
set(key: string, entry: CacheEntry, options?: { durable?: boolean }): Promise<void>Stores the complete CacheEntry under key. The entry contains its timestamp, lifetime, tags, type,
and payload.
durable: true requires the handler to resolve only after the entry is safely persisted.
Build prerenders rely on this because the process may exit immediately after
the write.
delete
delete(key: string): Promise<void>Removes the entry for key. Rebask calls it to clean up expired entries and entries that cannot be
deserialized.
revalidateTag
revalidateTag(tag: string): Promise<void>Marks entries carrying tag as expired for future reads. Tag invalidation does not need to scan and
delete every matching entry immediately.
getEntryStatus
getEntryStatus(entry: CacheEntry): CacheEntryStatusReturns whether an entry is stale or expired:
interface CacheEntryStatus {
/** Entry is past its `revalidate` window but still within `expire`. Safe to serve, should be revalidated. */
stale: boolean
/** Entry is past its `expire` window. Should not be served. */
expired: boolean
}An expired entry is never also stale. Tag-invalidated entries are dropped by get before their
status is read. Rebask uses this status to decide whether to serve an entry, refresh it in the
background, or render a new value.
tryClaim
tryClaim?(key: string): Promise<(() => void | Promise<void>) | null>tryClaim is optional. Implement it when multiple instances need to coordinate
background revalidation:
- Returns a release function when this instance claims
key. - Returns
nullwhen another instance already holds the claim or the handler does not persist that key. - Makes the claim atomic across instances and gives it a TTL so a crashed instance cannot block the key forever.
When tryClaim is not implemented, Rebask deduplicates background revalidation only within the
current process. The release function may be asynchronous and is called after revalidation finishes.
CacheEntry
import type { CacheEntry, CacheEntryType } from "@rebask/core/cache"A CacheEntry is an object with shared metadata and fields that depend on the entry type:
| Field | Description | Available on |
|---|---|---|
type | Identifies the entry type | Every entry |
timestamp | Creation time in milliseconds | Every entry |
revalidate | Number of seconds an entry remains fresh | Every entry |
expire | Number of seconds after which an entry cannot be served | Every entry |
tags | Optional tags used by revalidateTag | Every entry |
html | Rendered HTML | HTML entries |
postponed | Serialized resume state | HTML entries with postponed state (PPR) |
rsc | Encoded RSC payload | RSC and "use cache" entries |
data | Encoded JSON data | durableCache entries |
The handler should store and return each entry unchanged. The entry can be serialized as JSON.