Likely the first step is something like https://github.com/jacob-ebey/vite-plugin-react-use-cache/blob/2487fd1c3ee87917d49b78e3f735278253d8cdb1/packages/vite-plugin-react-use-cache/src/use-cache-plugin.ts#L62-L65. The following has a fuller story.
Context
This topic emerged while evaluating stable generated names for inline directive functions in PR #1257 and their use by Vinext PR #2156.
A generated inline name currently serves as both a Server Reference export name and, in Vinext, part of the cache definition key. PR #1257 makes that name stable across insertion of an unrelated directive function and changes it when the exact local function source changes. That can provide local body invalidation, but it does not respond to imported dependency changes and couples cache invalidation policy to JavaScript binding and Server Reference identity.
Development invalidation is better treated as a separate framework or Vite HMR concern.
Problem
When development uses a shared cache across requests, a cached function can continue returning stale output after its implementation or a server dependency changes:
// helper.js
export function getLabel() {
return 'before'
}
// cached.js
import { getLabel } from './helper.js'
export async function getCachedLabel() {
'use cache'
return getLabel()
}
If the cache key remains:
module identity + function identity + arguments
then changing helper.js to return 'after' does not necessarily invalidate the existing entry. Hashing only the source of getCachedLabel also misses this update because that function's local source did not change.
Production is a separate problem. A build or deployment identifier can namespace entries so different emitted builds do not share them. This request concerns invalidation while one development server remains running.
Expected Behavior
When development caching is enabled:
- Repeated calls without a relevant update hit the same entry.
- Editing a cached function causes its next call to miss and return updated output.
- Editing a direct or transitive server dependency causes affected cached functions to miss.
- Subsequent calls after the update hit the new entry.
- An unrelated update may preserve entries under a granular design or invalidate them under an intentionally coarse design, but it must not allow stale output.
Existing Approaches
Next.js HMR generation
Next.js includes a development hmrRefreshHash in "use cache" keys. The hot reloader advances it after server-component recompilation, and the request runtime composes it with build identity, function identity, and arguments. The implementation describes this as an intentionally coarse temporary solution in use-cache-wrapper.ts:1851.
This catches dependency changes because invalidation follows server graph recompilation rather than only the cached function's source text.
The dedicated use-cache-dev test suite provides useful behavioral references:
The custom-handler E2E separately asserts that development cache keys carry an optional trailing HMR refresh value.
No focused test in this suite edits a separate direct or transitive dependency of a cached function. A Vite implementation should add that coverage because its granular design would rely directly on reverse module-graph propagation.
Per-module Vite generations
vite-plugin-react-use-cache keeps a development generation counter for each transformed cache module. Its hotUpdate hook walks importers of changed modules and invalidates cache modules in that reverse dependency closure. Retransformation advances the module generation embedded in cache identity.
This is more granular than Next.js's global generation, but correctness depends on Vite module-graph coverage and HMR ordering.
Source-derived inline names
PR #1257 hashes the original function name and exact function source into an opt-in generated binding name. Vinext validates that prepending an unrelated cached function does not rename an existing inline cached function.
This establishes a downstream preference for unrelated-insertion stability, but it is not sufficient development invalidation:
- Imported dependency edits do not change the hash.
- Whitespace and comment edits do change the hash.
- Cache identity becomes coupled to Server Reference export naming.
- Exact duplicate functions still require traversal-based disambiguation.
Vinext currently bypasses shared cache lookup and storage in development, so its use of stableName does not by itself demonstrate a live development cache invalidation requirement. The intended consumer should be confirmed before changing plugin-rsc's naming contract.
Design Options
Global RSC development epoch
Maintain one generation for the development RSC graph and include it in every framework cache key.
Advantages:
- Closest to current Next.js behavior.
- Simple stale-output safety model.
- Does not require per-function source identity.
Costs:
- Any relevant server update invalidates all development cache entries.
- The epoch must be visible consistently to the Vite plugin, module runner, and request runtime.
Affected cache-module epochs
Track modules containing cached functions. On update, walk reverse importers from changed RSC modules, invalidate affected cache modules, and advance only their generations.
Advantages:
- Preserves unrelated development entries.
- Naturally fits Vite's unbundled module graph.
Costs:
- Must cover direct, transitive, virtual, and dynamically loaded dependencies that Vite tracks.
- Must ensure invalidated cache modules are retransformed before their next execution.
- More HMR lifecycle and graph behavior needs verification.
Dependency-derived implementation hashes
Compute a hash of each cached function's relevant server dependency subgraph.
Advantages:
- Can provide precise implementation identity.
- Could potentially support controlled reuse beyond one development generation.
Costs:
- Significantly more build-graph analysis.
- Hard to define relevant dependencies at function granularity.
- Unnecessary if production builds already use deployment namespaces.
This should not be the first implementation unless a durable cross-deployment requirement appears.
Proposed Research Steps
- Confirm with the Vinext author whether
stableName is intended for development cache invalidation, persistent cache identity, Server Reference continuity during HMR, or only transform determinism.
- Establish a minimal development cache reproduction that remains stale after editing the cached function body.
- Extend the reproduction with direct and transitive imported dependencies.
- Verify when Vite calls
transform after source and dependency updates and whether manually invalidated reverse importers are reevaluated before the next request.
- Prototype a coarse global RSC epoch as the correctness baseline.
- Prototype per-cache-module generations using
hotUpdate and reverse importer traversal.
- Compare invalidation coverage, unnecessary misses, lifecycle complexity, and behavior after HMR errors.
- Decide whether any generic plugin-rsc API is required or whether the framework cache plugin can own the complete mechanism.
Architecture Boundary
The cache framework should own whether development entries survive HMR and how an invalidation generation enters its cache key. plugin-rsc may need to expose a Vite/RSC lifecycle hook only if framework plugins cannot observe the required graph updates reliably.
The inline hoister should continue to provide valid generated exports and the context needed by its runtime-expression callback. Stable cache invalidation should not require changing generated binding names unless Server Reference continuity independently requires that behavior.
Verification Matrix
- Cached function body edit.
- Direct imported server dependency edit.
- Transitive imported server dependency edit.
- Unrelated server module edit.
- Client-only dependency edit.
- Two cached functions in one module when only one local body changes.
- Import addition, removal, and replacement after HMR.
- HMR syntax error followed by recovery.
- Development server restart.
- Repeated calls before and after each update to prove miss-then-hit behavior.
- A Client Component invocation path when cached functions participate in Server Function transport.
Scope
This topic does not decide:
- Production build or deployment cache namespaces.
- Cross-deployment cache reuse.
- Cache handlers, lifetime, tags, or storage policy.
- Closure encryption or caller-argument admission.
- Stable Server Reference export names across HMR. That should be justified by a separate reference-continuity reproduction if needed.
"use cache"Server Functions #1336Likely the first step is something like https://github.com/jacob-ebey/vite-plugin-react-use-cache/blob/2487fd1c3ee87917d49b78e3f735278253d8cdb1/packages/vite-plugin-react-use-cache/src/use-cache-plugin.ts#L62-L65. The following has a fuller story.
Context
This topic emerged while evaluating stable generated names for inline directive functions in PR #1257 and their use by Vinext PR #2156.
A generated inline name currently serves as both a Server Reference export name and, in Vinext, part of the cache definition key. PR #1257 makes that name stable across insertion of an unrelated directive function and changes it when the exact local function source changes. That can provide local body invalidation, but it does not respond to imported dependency changes and couples cache invalidation policy to JavaScript binding and Server Reference identity.
Development invalidation is better treated as a separate framework or Vite HMR concern.
Problem
When development uses a shared cache across requests, a cached function can continue returning stale output after its implementation or a server dependency changes:
If the cache key remains:
then changing
helper.jsto return'after'does not necessarily invalidate the existing entry. Hashing only the source ofgetCachedLabelalso misses this update because that function's local source did not change.Production is a separate problem. A build or deployment identifier can namespace entries so different emitted builds do not share them. This request concerns invalidation while one development server remains running.
Expected Behavior
When development caching is enabled:
Existing Approaches
Next.js HMR generation
Next.js includes a development
hmrRefreshHashin"use cache"keys. The hot reloader advances it after server-component recompilation, and the request runtime composes it with build identity, function identity, and arguments. The implementation describes this as an intentionally coarse temporary solution in use-cache-wrapper.ts:1851.This catches dependency changes because invalidation follows server graph recompilation rather than only the cached function's source text.
The dedicated use-cache-dev test suite provides useful behavioral references:
The custom-handler E2E separately asserts that development cache keys carry an optional trailing HMR refresh value.
No focused test in this suite edits a separate direct or transitive dependency of a cached function. A Vite implementation should add that coverage because its granular design would rely directly on reverse module-graph propagation.
Per-module Vite generations
vite-plugin-react-use-cache keeps a development generation counter for each transformed cache module. Its
hotUpdatehook walks importers of changed modules and invalidates cache modules in that reverse dependency closure. Retransformation advances the module generation embedded in cache identity.This is more granular than Next.js's global generation, but correctness depends on Vite module-graph coverage and HMR ordering.
Source-derived inline names
PR #1257 hashes the original function name and exact function source into an opt-in generated binding name. Vinext validates that prepending an unrelated cached function does not rename an existing inline cached function.
This establishes a downstream preference for unrelated-insertion stability, but it is not sufficient development invalidation:
Vinext currently bypasses shared cache lookup and storage in development, so its use of
stableNamedoes not by itself demonstrate a live development cache invalidation requirement. The intended consumer should be confirmed before changing plugin-rsc's naming contract.Design Options
Global RSC development epoch
Maintain one generation for the development RSC graph and include it in every framework cache key.
Advantages:
Costs:
Affected cache-module epochs
Track modules containing cached functions. On update, walk reverse importers from changed RSC modules, invalidate affected cache modules, and advance only their generations.
Advantages:
Costs:
Dependency-derived implementation hashes
Compute a hash of each cached function's relevant server dependency subgraph.
Advantages:
Costs:
This should not be the first implementation unless a durable cross-deployment requirement appears.
Proposed Research Steps
stableNameis intended for development cache invalidation, persistent cache identity, Server Reference continuity during HMR, or only transform determinism.transformafter source and dependency updates and whether manually invalidated reverse importers are reevaluated before the next request.hotUpdateand reverse importer traversal.Architecture Boundary
The cache framework should own whether development entries survive HMR and how an invalidation generation enters its cache key. plugin-rsc may need to expose a Vite/RSC lifecycle hook only if framework plugins cannot observe the required graph updates reliably.
The inline hoister should continue to provide valid generated exports and the context needed by its runtime-expression callback. Stable cache invalidation should not require changing generated binding names unless Server Reference continuity independently requires that behavior.
Verification Matrix
Scope
This topic does not decide: