Skip to content

feat(cache): allow server-only caching in defineCachedEventHandler (disable Cache-Control headers) #3997

@Ice-Hazymoon

Description

@Ice-Hazymoon

Describe the feature

defineCachedEventHandler currently couples Nitro server storage caching with response cache headers by automatically emitting Cache-Control based on maxAge.

In some setups, this makes it impossible to treat Nitro cache as server-only, because browsers or CDNs may cache the response and stop requests from reaching the server entirely. Once that happens, shouldBypassCache / shouldInvalidateCache are never evaluated, and server-side invalidation logic becomes ineffective.


CDN context

In practice, some CDNs (e.g. Cloudflare) do not reliably honor Vary when constructing cache keys (see: https://developers.cloudflare.com/cache/concepts/cache-control/), which means varies may not work as expected on the client/edge even if Nitro emits it correctly.

As a result, response caching can escape Nitro’s control even when varies is configured.


Reproduction

export default defineCachedEventHandler(async () => {
  return { ts: Date.now() }
}, {
  maxAge: 60,
  varies: ['x-foo']
})

Observed:

  • Nitro caches correctly in server storage
  • Cache-Control is sent to the client
  • Browser/CDN may cache and never revalidate
  • Subsequent requests may never reach Nitro

Expected (for some use cases):

  • Server storage cache is used
  • Client is not instructed to cache
  • Cache correctness and invalidation remain server-controlled

Workaround

Using defineCachedFunction instead of defineCachedEventHandler avoids emitting response cache headers and works as a server-only cache, but it loses the event handler semantics and is not always a good fit.


Proposed solution

Allow defineCachedEventHandler to opt out of emitting cache-related response headers, while still using server storage caching.

Example API shapes:

defineCachedEventHandler(handler, {
  maxAge: 60,
  sendHeaders: false
})

or

defineCachedEventHandler(handler, {
  maxAge: 60,
  cacheControl: false
})

Default behavior would remain unchanged.


Why this matters

  • Server-side caching and client/CDN caching are separate concerns in real deployments
  • External caches are common and not always fully spec-compliant
  • Nitro should not lose control of its cache invalidation logic due to response headers

Related: #2738

English is not my first language, and I used AI to help refine the wording of this issue.

Additional information

  • Would you be willing to help implement this feature?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions