On this page

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 RebaskConfig

For 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 invalidatedAt to answer when a tag was last invalidated, and record it in revalidateTag.
  • Eager — delete the tagged entries in revalidateTag, and leave invalidatedAt as 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): CacheEntryStatus

Returns 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 null when 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:

FieldDescriptionAvailable on
typeIdentifies the entry typeEvery entry
timestampCreation time in millisecondsEvery entry
revalidateNumber of seconds an entry remains freshEvery entry
expireNumber of seconds after which an entry cannot be servedEvery entry
tagsOptional tags used by revalidateTagEvery entry
htmlRendered HTMLHTML entries
postponedSerialized resume stateHTML entries with postponed state (PPR)
rscEncoded RSC payloadRSC and "use cache" entries
dataEncoded JSON datadurableCache entries

The handler should store and return each entry unchanged. The entry can be serialized as JSON.