Skip to content

Support inline "use cache" exports in file-level "use server" modules and vice versa #1394

Description

@hi-ogawa

While #1310 introduced framework-owned server reference plugin api, it turned out to be not sufficient to support mixed file/inline case as demonstrated in #1315.

I don't have a solution but this should be tracked as independent work item.


Problem

A practical mixed-directive module is a file-level "use server" module with one exported cached function:

'use server'

export async function updateProduct(formData) {
  // action
}

export async function getProduct(id) {
  'use cache'
  return db.product(id)
}

updateProduct should inherit the file-level Server Function role. The explicit inline directive should override that default for getProduct, so its framework-owned cache wrapper becomes the canonical callable and Server Reference target.

The reverse direction is also coherent:

'use cache'

export async function getProduct(id) {
  return db.product(id)
}

export async function updateProduct(formData) {
  'use server'
  // mutation
}

Here getProduct inherits the cached module role while updateProduct explicitly opts into the built-in action role.

Current limitation

The built-in "use server" transform and a framework-owned "use cache" transform independently select and lower complete modules or functions. They cannot reliably implement an override by plugin order because the first transform can already commit wrappers, generated exports, proxies, and Server Reference claims before the second transform classifies the original callable.

Running the cache transform first allows the later file-level server transform to treat cache-owned output as ordinary "use server" exports. Running the server transform first allows it to consume the module and generate browser or SSR proxies before the cache transform can preserve different ownership for the inline override. Server Reference claim aggregation can detect duplicate ownership after the fact, but it cannot recover the intended source role.

Expected classification

The two directive owners need to agree on source roles before committing lowering:

file-level directive supplies the default role
  -> explicit function directive overrides the default
  -> exported functions without an explicit role inherit the default
  -> local functions require an explicit role
  -> each reference has one canonical callable, proxy identity, and owner

The same classification must drive RSC lowering, browser and SSR proxies, and Server Reference claims. The implementation could use one role-aware traversal, a shared source-role map, or another ownership handoff. The issue does not require a specific API upfront.

Next.js behavior

Next.js classifies file and function directives in one Server Actions visitor and gives an explicit function directive precedence over the file default.

  • Fixture 48 covers an exported cache override in a "use server" module.
  • Fixture 49 covers a default export cache override.
  • Fixture 51 covers inline "use server" overrides in a "use cache" module.

These fixtures demonstrate the role-precedence behavior without requiring plugin-rsc to adopt Next.js's cache runtime or generated code.

Scope

This issue covers mixed ownership between the built-in "use server" directive and a framework-owned directive such as "use cache".

It does not cover:

Verification

  • In a "use server" module, an inline "use cache" export uses the framework cache wrapper and is not re-owned by the built-in server transform.
  • Other exports in that module retain built-in "use server" behavior.
  • In a "use cache" module, an inline "use server" export uses the built-in action runtime while other exports retain the cache role.
  • Named and default export overrides resolve through the correct browser and SSR proxies.
  • Each callable has one canonical Server Reference target and one owner claim.
  • Both directions work through Client Component invocation 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