Conrad caching

Nitro's cache

defineCachedEventHandler, cachedFunction and route-rule cache — every option, what swr does on the server and in the header, where entries live, sharing them through Redis or Valkey, and the headers the browser should get for cached HTML.

From the Nitro caching part of your Conrad Caching note.

Know

  • Three ways in: defineCachedEventHandler for a route, cachedFunction (alias defineCachedFunction) for any async function, and cache in route rules for pages.
  • The key: base:group:name:key.json, so with base: 'redis', the default group nitro/handlers, a handler named product and getKey returning the id, the entry is redis:nitro/handlers:product:123.json. Predictable keys are what make purging possible.
  • Options: maxAge (fresh for this many seconds, default 1), swr (serve stale and refresh in the background, default on), staleMaxAge (how long past maxAge stale is still allowed, default unlimited), getKey and varies (what counts as the same page), group and name (the key's namespace), base (which storage mount), shouldBypassCache and shouldInvalidateCache (per request).
  • Where entries live: memory of each instance in production, the filesystem in development, unless base points at a mount. A server plugin can mount Redis or Valkey at runtime from runtimeConfig, so one cache serves every pod.
  • The header it sends: a cached handler also writes Cache-Control: public, max-age=<maxAge>, s-maxage=<maxAge> and, with swr, stale-while-revalidate, plus an ETag of the body. For HTML that is usually rewritten to private, no-cache so browsers revalidate every time and the CDN stays out of it.
  • Purging: by exact key (removeItem), by prefix (getKeys then remove, which catches every variant), or by tag, which Nitro does not have, so you keep your own index of keys per product. Hard purge deletes, soft purge marks stale so the next visitor still gets the old copy while a fresh one renders. Redis, the CDN and the browser are purged separately; the browser cannot be, it relies on the ETag.
  • Server-only data: a cached handler whose output must not sit in a browser or CDN sends Cache-Control: no-store; the server cache still works.
  • Two different lifetimes: maxAge and staleMaxAge are Nitro's rules; the TTL of the Redis key is the storage's rule and can be much longer.

Fact check of your notes

Gotcha· The 3600 is a project value, and the header is s-maxage

"handleCacheHeaders sets public, max-age=3600, s-max-age=3600": the numbers follow the handler's maxAge, so a handler with maxAge: 3600 sends them, while Nitro's default is 1 second. The directive is spelled s-maxage. With swr on, stale-while-revalidate is appended too.

Gotcha· group is the key namespace

The empty group line: group (default nitro/handlers) and name form the prefix of every key, so entries of one handler can be cleared together with useStorage('cache').clear('nitro:handlers:…').

Gotcha· Spelling

stale-while-revalidate (SWR), not SWL.

Exercise

Exercise
  • In a local Nuxt app, wrap a slow server route in defineCachedEventHandler with maxAge: 10, call it three times and watch the response headers and timing; then set swr: false and compare.
  • Mount Redis in a server plugin, set base: 'redis' and confirm with redis-cli keys '*' that the entries moved there.
  • Write the route rule that caches /products/** for 60 seconds per device type and leaves /account/** alone, from memory.

Be able to say

Be able to say· Where does Nitro's cache live, and how do you share it between instances?

"By default in the memory of each instance, which means every pod has its own cache and loses it on restart. To share it you mount a storage such as Redis, in a server plugin so the URL can come from runtime config, and point the cache at it with base: 'redis'. Then every pod reads and writes the same entries, a miss on one pod is a hit on the next, and a deploy does not empty the cache."

Be able to say· What does swr: true actually do?

"Two things. On the server, an expired entry is served immediately and re-rendered in the background, so nobody waits for a render after the first one. In the response, it adds stale-while-revalidate to the Cache-Control the handler sends, suggesting the same behaviour to shared caches. For HTML I keep the first and drop the second, because the browser should revalidate every time and the CDN should not cache pages with many variants."

Your notes

Open in Notes

Nothing yet. Notes filed here show up on this page and the section overview.