Cache-Control & Eligibility
defineCachedHandler synthesizes a Cache-Control header for cacheable responses, but it always defers to what the handler sets and to a set of built-in eligibility rules. This page covers how to keep responses out of the cache, decouple server-side storage from client caching, and add your own eligibility rules.
What is never cached
Before storing a response, ocache applies these built-in checks. A response failing any of them is still returned to the caller, but is never stored or served from the cache:
4xx/5xxstatuses — only successful responses are cached.Cache-Control: no-storeorprivate— an explicit opt-out from the handler.- Empty body — nothing to replay.
- Missing
etag/last-modified— required for conditional requests (ocache adds these itself when the handler omits them, so this mainly guards handler-set placeholder values).
These checks always apply and cannot be turned off.
Private / non-cacheable responses
To keep a specific response out of the cache from inside the handler, set no-store or private:
const handler = defineCachedHandler(
async (event) => {
const user = await getUser(event);
if (!user) {
return new Response("Please log in", { status: 401 });
}
// Personalized — never cache this one
return new Response(renderDashboard(user), {
headers: {
"content-type": "text/html",
"cache-control": "private",
},
});
},
{ maxAge: 300 },
);
Any other Cache-Control the handler sets is preserved verbatim — ocache only synthesizes max-age / s-maxage / stale-while-revalidate when the handler didn't set a Cache-Control of its own.
no-store / private only controls what gets stored — it does not partition the cache key. Concurrent requests are still coalesced by cache key, so responses that differ per user must be keyed correctly (via varies or allowCookies) or a personalized body could be shared.Server-only caching (sendCacheControl)
Sometimes you want to cache a response in your own storage (to save recomputing it) while telling browsers and CDNs not to cache it — for example a page that is expensive to build but must always be revalidated downstream. Reaching for no-store / private doesn't work here: those also disqualify the response from storage.
Set sendCacheControl: false to decouple the two. The response is still stored and served from cache (SWR, etag, and last-modified all still apply), but no Cache-Control header is emitted:
const handler = defineCachedHandler(myHandler, {
maxAge: 60,
swr: true,
sendCacheControl: false, // stored & served from cache, but nothing advertised downstream
});
This only governs ocache's own synthesis — a Cache-Control the handler sets explicitly is still preserved and sent.
invalidateCache/expireCache clears your storage but callers still get stale responses, a browser or CDN in front of the handler is likely serving from its own copy, cached independently via the synthesized Cache-Control — a purge to server storage never reaches it. sendCacheControl: false avoids this by never letting the client/CDN cache the response in the first place.Custom eligibility (shouldCache)
Use shouldCache to add your own rejection rule on top of the built-in checks. It receives the serialized response and returns false to skip caching (the response is still returned to the caller). For example, to keep 3xx redirects out of the cache, which the built-in checks would otherwise allow:
const handler = defineCachedHandler(myHandler, {
maxAge: 60,
shouldCache: (res) => res.status < 300 || res.status >= 400,
});
Key behaviors:
- ANDed with the built-ins.
shouldCachecan only narrow what gets cached — it can never force-cache a response the built-in checks reject. - May be async. Return a
booleanor aPromise<boolean>. - Runs on reads too. It gates both storing a fresh response and serving a stored one (including the stale-while-revalidate serve decision), so keep it fast and decide only from the passed entry.
- Fails closed. A throwing hook is treated as non-cacheable and reported via
onError.
Related
headersOnlyskips response storage entirely and only answers conditional (304) requests — reach for it when you want the304shortcut but no full-body caching.- Incremental Static Regeneration (ISR) builds on
swr+maxAgeto serve stale content while revalidating.