You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit c3501ea
Browse filesBrowse the repository at this point in the historyBrowse files
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:
13
13
14
14
-**Any status outside `200`, `203`, `301`, `308`** β the storable statuses are an explicit allowlist, not "anything that isn't an error". Those four are the ones whose response is a complete, reusable representation of what was requested. Everything else is refused, including `4xx`/`5xx` (an error is not a representation), `201`/`202`/`300` (the outcome of an operation, or an unresolved choice), `204`/`205`/`304` (nothing to replay β a `304` in particular answers one client's conditional request), `206` (a partial body, valid only for the range that was asked for) and the temporary redirects `302`/`303`/`307` (a per-request answer: an auth redirect to `/login` stored under an anonymous key would be served to everyone else hitting that URL).
15
-
-**`Cache-Control: no-store` or `private`** β an explicit opt-out from the handler.
15
+
-**A `Cache-Control` opt-out from the handler** β `no-store`, `private`, `no-cache`, or a zero **shared** lifetime (`s-maxage` when present, `max-age` otherwise). See [Private / non-cacheable responses](#private--non-cacheable-responses).
16
+
-**`Vary: *`** β the strongest "do not share this" signal short of `no-store`: no stored response may ever match such a request (RFC 9111 Β§4.1). ocache keeps one entry per cache key, so the only honest handling is to refuse the entry.
16
17
-**Empty body** β nothing to replay.
17
18
-**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).
18
19
19
20
These checks always apply and cannot be turned off.
20
21
21
-
A non-storable **status** also suppresses `Cache-Control` synthesis: ocache never advertises a lifetime for a response it will not store itself. A `500` is returned to the caller with no synthesized `Cache-Control` at all, so a shared cache in front of your handler doesn't pin the error for `maxAge + staleMaxAge` while ocache re-runs the handler on every request. (A `Cache-Control` the handler set itself is always preserved, whatever the status.)
22
+
A response the built-in checks refuse also gets **no synthesized `Cache-Control`** β with one deliberate exception, `shouldCache`, below. A `500` is returned to the caller with no synthesized `Cache-Control` at all, so a shared cache in front of your handler doesn't pin the error for `maxAge + staleMaxAge` while ocache re-runs the handler on every request; the same goes for a `Vary: *` response. The `Cache-Control` opt-outs need no special handling here: they _are_ a `Cache-Control` the handler set, and ocache never overwrites one (whatever the status), so its directive is what reaches the client.
23
+
24
+
The exception is [`shouldCache`](#custom-eligibility-shouldcache): a response your own hook rejects is still advertised with the synthesized lifetime. That is on purpose β "ocache doesn't keep this, but a CDN may" is a legitimate setup, and `sendCacheControl: false` is its inverse ("ocache keeps this, but nobody downstream may"). Reach for that one, or set a `Cache-Control` on the response, if you want the advertisement to go away too.
22
25
23
26
## Private / non-cacheable responses
24
27
25
-
To keep a specific response out of the cache from inside the handler, set `no-store` or `private`:
28
+
To keep a specific response out of the cache from inside the handler, set a `Cache-Control` that says so. All of these are honored β the response is returned to the caller with its header intact, but never stored and never served from the cache:
|`private`| Not in a shared cache β and ocache is one. |
34
+
|`no-cache`| Must not be reused without revalidating first. |
35
+
|`max-age=0` / `s-maxage=0`| Stale the moment it was produced. |
36
+
37
+
Directives are parsed properly, not substring-matched: `max-age=0600` is 600 seconds, and a `0` in some other directive's value (`stale-while-revalidate=0`) is not a zero lifetime.
38
+
39
+
`s-maxage` overrides `max-age` for shared caches (RFC 9111 Β§5.2.2.10), and ocache is one β so when both are set, only `s-maxage` decides. `public, max-age=0, s-maxage=600` ("browsers revalidate, shared caches keep it for 10 minutes") is cached for 600 seconds, and `public, s-maxage=0, max-age=600` is not cached at all.
40
+
41
+
> [!NOTE]
42
+
> The same rule applies to the header ocache synthesizes for itself: configuring `maxAge: 0` advertises a zero lifetime (`max-age=0`, or `s-maxage=0` under `swr`) and therefore stores nothing. That is what a zero lifetime means β such an entry would be expired the moment it was written, so the handler runs on every request either way.
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.
47
64
48
65
> [!IMPORTANT]
49
-
> `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`](/guide/handler) or [`allowCookies`](/guide/cookies)) or a personalized body could be shared.
66
+
> An opt-out 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`](/guide/handler) or [`allowCookies`](/guide/cookies)) or a personalized body could be shared.
67
+
68
+
> [!NOTE]
69
+
> `no-cache` is currently rejected outright rather than stored-and-always-revalidated. RFC 9111 Β§5.2.2.4 permits storing such a response as long as it is revalidated with the origin before every reuse; ocache has no foreground-revalidation path today, so "stored but always revalidated" would buy nothing over not storing.
70
+
71
+
## Never serve this stale (`must-revalidate`)
72
+
73
+
`must-revalidate` is **not** an opt-out: it constrains stale serving, not storage. A response carrying it is stored and served from cache normally while it is fresh β but it is never served stale, even with `swr: true` and a wide `staleMaxAge`. Once the entry expires, the next request revalidates in the **foreground** and waits for the fresh response:
{ maxAge: 60, swr: true, staleMaxAge: 600 }, // SWR everywhere else, never for this response
83
+
);
84
+
```
85
+
86
+
Internally this is a per-entry `staleMaxAge: 0` β the same mechanism [`getMaxAge`](/guide/api) uses to give one entry its own lifetime β so it applies to that response only, not to the whole handler.
50
87
51
88
## Server-only caching (`sendCacheControl`)
52
89
53
-
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.
90
+
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 (and every other opt-out above) also disqualify the response from storage.
54
91
55
92
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:
Copy file name to clipboardExpand all lines: docs/1.guide/9.isr.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -30,7 +30,7 @@ Two options make it ISR-like:
30
30
With this config the handler also emits `Cache-Control: s-maxage=60, stale-while-revalidate`, so any shared/CDN cache in front of it revalidates on the same schedule. (Set `staleMaxAge` and the directive becomes `s-maxage=60, stale-while-revalidate=<staleMaxAge>`.)
31
31
32
32
> [!NOTE]
33
-
> If your handler sets its own `Cache-Control`, ocache leaves it untouched and won't synthesize one. See [Cache-Control](/guide/cache-control).
33
+
> If your handler sets its own `Cache-Control`, ocache leaves it untouched and won't synthesize one β and that header is also read: an opt-out (`no-store`, `private`, `no-cache`, `max-age=0`) keeps the page out of the cache entirely, and `must-revalidate` keeps it cached but never served stale, which switches ISR off for that page. See [Cache-Control](/guide/cache-control).
0 commit comments