feat: add cache() middleware for server-side response caching#3782
feat: add cache() middleware for server-side response caching#3782bartlomieju wants to merge 1 commit into
Conversation
Adds a first-party middleware that caches responses using the Web Cache API. Routes opt in via standard Cache-Control headers — no new config surface needed. Supports stale-while-revalidate for ISR-like background revalidation. Closes #8
|
Nice work — the implementation is clean and follows the existing middleware conventions well. A few things that aren't covered here that might be worth considering as follow-ups: Pluggable cache backend — This uses the Web Cache API exclusively, which works great on Deno Deploy but ties caching to the runtime. A CDN cache coordination — For apps behind a CDN (Cloudflare, Fastly, etc.), it's common to need
None of these block the initial merge — the current scope is solid and the SWR via |
lunadogbot
left a comment
There was a problem hiding this comment.
CI is red — Type check project is failing on all six matrix jobs (the workflow log just prints error: Type checking failed. without the offending file, so worth re-running with verbose output to surface it). That's a hard blocker for approval.
A few concrete things in packages/fresh/src/middlewares/cache.ts worth addressing while you're at it:
- Custom
shouldCachebypasses the default safety net. Line 104 swaps in the user's predicate wholesale, sostatus !== 200,Set-Cookie,private/no-store, and the?fresh-partialskip all silently disappear. The test on line 311 caches a response that has noCache-Controlheader at all — almost certainly not what a user expects. Consider AND-ing the user predicate with the built-in checks (or at minimum documenting the trade-off onCacheOptions.shouldCache). methods: ["POST"|"HEAD"|...]will throw at runtime. Per spec,Cache.putrejects non-GET requests with aTypeError. Thecustom methodstest on line 337 declares["GET", "HEAD"]but only exercisesserver.get, so the HEAD path is uncovered — first real HEAD request will explode instore.put. Either restrictmethodsto GET-only or guard the put.- Body-stream leak on corrupt entries. Line 145's
await cached.body?.cancel()lives insideif (cachedAtStr !== null). If a cachedResponseis missingX-Fresh-Cached-At(older middleware version, manual cache writes), the body never gets canceled before the fall-throughctx.next().
- nit: the maintainer self-comment already flags
Vary, pluggable backend, and CDN headers as follow-ups — agree on all three, especiallyVary, which is the most common foot-gun.
Summary
cache()middleware that uses the Web Cache API for server-side response cachingCache-Controlresponse headers — no new config surface or framework-specific DSL neededstale-while-revalidatefor ISR-like behavior (serve stale immediately, regenerate in background)private/no-store,Set-Cookie, non-200, and partial requestscacheName,methods, and customshouldCachefunctionstaticFiles(),cors(), etc.)Usage
Routes opt in:
Closes #8
Test plan