Cache

The cache is for values you can afford to lose and recompute: an expensive computation, a third-party API response. Each value has a time to live (TTL) and expires after it. For data that must last, use the KV store.

API

The framework provides createCache, imported from @bext-stack/framework/kv. It works over any store that implements CacheStore (get, set, delete). The kvStore store keeps values in your app's KV:

import { createCache, kvStore } from "@bext-stack/framework/kv";

// Shared across requests, scoped to your app, stored in KV with its TTL
const cache = createCache({ store: kvStore({ appId: "<app-id>" }), prefix: "rates" });

const rates = await cache.remember("eur", 300, () => fetchRates("EUR")); // TTL in seconds
await cache.put("flag", true, 60);
await cache.get<boolean>("flag"); // true | null
await cache.forget("flag");

Values are JSON-encoded, so you cache typed objects, not just strings. They must therefore be JSON-serializable (a Date comes back as a string).

Method Description
get<T>(key) Read a value, or null if it is absent or expired.
put(key, value, ttlSecs?) Write a value. Without a TTL (or with 0), it does not expire.
remember(key, ttlSecs, factory) Read the value, or compute it with factory, store it and return it.
has(key) Whether a value is present.
forget(key) Remove a value.
pull<T>(key) Read then remove a value.
add(key, value, ttlSecs?) Write only if the key is absent; returns true when written.

Choosing a store

Store Scope
kvStore({ appId }) Shared across requests, scoped to your app, stored in KV with its TTL.
memoryStore() In memory, local to the instance running the code. Good for short-lived caching and tests.

You can also supply your own store: an object with get(key), set(key, value, ttlSecs?) and delete(key), sync or async. get must return exactly the string that set received, or null.

The prefix option prefixes every key (rates:eur), which keeps cache keys apart from your other KV keys. remember treats only null as a miss: false, 0 and "" are cached like any other value. A factory that returns null, however, runs again on every read.

Caching a loader

To cache a page loader's result, wrap it in cachedLoader. The cache is shared by all render workers and keyed by host, route, route params and query string. Cookies and headers are not part of the key, so only wrap loaders whose output does not depend on the visitor.

import { cachedLoader } from "@bext-stack/framework";

export const loader = cachedLoader(
  { ttl: 60, tags: ["projects"] }, // ttl in seconds, 60 when omitted
  async ({ params }) => ({ project: await getProject(params!.slug) }),
);
Option Description
ttl Time to live in seconds. Omitted or 0: 60 seconds.
tags Tags used to invalidate entries with revalidateTag.
vary A (args) => string function that adds key material, such as a locale.

Only GET requests go through the cache. A null or undefined result or a Response is not cached, and the result is stored as JSON.

revalidateTag("projects"), imported from @bext-stack/framework/cache, drops the entries carrying that tag and returns the number of entries removed.

import { revalidateTag } from "@bext-stack/framework/cache";

revalidateTag("projects");

Cache or KV

Cache (createCache) KV (sdk.kv)
Use Recomputable values Durable data
Lifetime Short, expires after the TTL Kept until deleted (optional TTL)
Typical pattern remember(key, ttl, factory) get / set / delete / list

Use the cache to memoize an expensive computation or a response you can fetch again. Use KV when the data must survive: session-related state, job records, anything you cannot recompute.

Where to next

Page What it covers
KV Store The durable string store
SDK Overview The createSdk client