Skip to content

Exclude Undeclared Caller Arguments From "use cache" Cache Keys #1392

Description

@hi-ogawa

This issue tracks another minor independent divergence from Next.js use cache semantics which requires information provided by ast transform.

useActionState scenario sounds a bit practical use case, but otherwise, this feature may not crucially affect base use cache behavior.


Problem

Callers and frameworks can provide more arguments than a cached function declares. These undeclared arguments should not affect its cache key.

This applies to cached functions selected by either a file-level directive or an inline function directive.

A practical example is useActionState, which supplies previous state and form data even when the action declares no parameters:

async function getCachedRandomValue() {
  'use cache'
  return Math.random()
}

await getCachedRandomValue(previousStateA, formDataA)
await getCachedRandomValue(previousStateB, formDataB)

Both calls should invoke and key the cached function with an empty argument list, so they address the same cache entry.

Argument Admission

For a statically known function:

  • A fixed signature admits only its declared positional arguments.
  • A zero-parameter function admits no caller arguments.
  • A rest parameter admits all remaining arguments.
  • An unknown signature conservatively admits all arguments.

For example:

async function getProduct(accountId, productId) {
  'use cache'
}

getProduct('account-7', 'product-1', 'framework-extra')

The admitted invocation is:

getProduct('account-7', 'product-1')

and its cache arguments are:

['account-7', 'product-1']

The implementation must receive the same admitted arguments used for the key. Otherwise an omitted argument could affect execution without affecting cache identity.

For an inline cached function, transform-bound closure captures are separate from caller-supplied invocation arguments. Argument admission applies after that boundary is identified:

[protected capture envelope, ...caller arguments]
                    |
                    v
decoded captures + admitted caller arguments

For example, a fixed two-parameter inline function retains its decoded captures and the first two caller arguments while excluding later framework-supplied arguments. A rest parameter continues to admit all caller arguments. The capture envelope or equivalent boundary is handled separately by #1393.

Transform Requirement

The source transform knows the declared parameter count and whether the function has a rest parameter. This information cannot be recovered reliably from Function.length because default parameters truncate its value, rest parameters are omitted, and generated wrappers can obscure the original signature.

The transform can either:

  • Expose metadata such as { count, hasRest } to the framework runtime.
  • Emit the required argument slicing directly.

Unknown functions and unresolved re-exports should use a pass-all fallback.

The same source parameter shape is sufficient for file-level and inline directives. Inline lowering additionally composes it with the capture boundary from issue #1393. A self-describing protected envelope lets the framework wrapper remove that slot before applying { count, hasRest }. If captures remain directly bound as multiple arguments instead, the wrapper needs their generated bound-slot count or an equivalent adapter. That is a capture-boundary representation choice, not a second parameter-admission primitive.

Next.js emits an empty list, a fixed prefix slice, or all arguments based on the source signature in server_actions.rs.

Scope

This issue covers declared caller-argument admission only.

It does not cover:

  • The closure capture encryption or boundary representation itself, tracked by Support use cache with closure encryption #1393. This issue only composes with that boundary for inline functions.
  • Analysis of whether a declared parameter is actually used.
  • Cache storage, serialization, invalidation, or lifetime.
  • Generated reference identity.

Next.js currently retains every declared parameter rather than performing parameter-usage analysis.

Verification

  • Zero-parameter functions ignore caller-supplied arguments.
  • Fixed signatures admit only their declared positional prefix.
  • Different admitted arguments produce different entries.
  • Rest parameters admit all remaining arguments.
  • Default and destructured parameters count as declared positional inputs.
  • Unknown signatures conservatively admit all arguments.
  • File-level and inline directives apply the same caller-argument admission.
  • Inline captures remain in cache identity while undeclared caller arguments are excluded.
  • The behavior works in development and production.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions