`use cache` form action misses cache after hydrated reload
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 142k
- Forks
- 32.4k
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 351
Description
Link to the code that reproduces this issue
https://github.com/hi-ogawa/reproductions/tree/main/next-use-cache-form-reload
To Reproduce
The primary scenario is:
- Load
/inlinedirectly and wait for hydration. - Select Reset cache.
- Submit the cached form with
message=hello. - Confirm the cached function executed once.
- Reload the page and wait for hydration.
- Submit the unchanged form again.
- Observe that the cached function executes a second time.
The relevant source is equivalent to:
const captured = 'captured'
async function cachedAction(formData: FormData) {
'use cache'
// it shows logs for each form action after reload and hydration
process.stdout.write(
`cachedAction ${captured} ${JSON.stringify([...formData])}\n`,
)
}
<form action={cachedAction}>
<input name="message" defaultValue="hello" />
<button>Submit</button>
</form>
The reproduction uses process.stdout.write() because cached functions replay captured console output and it can be misleading for verifying the original function execution.
In the reproduction project, the behavior can be confirmed by:
pnpm install
pnpm test:dev
pnpm test:build
Current vs. Expected behavior
The same result occurs in development and production:
| Form path | Inline directive | File directive |
|---|---|---|
| Direct SSR load, hydrated reload | 2 executions | 1 execution |
| Client navigation, then hydrated reload | 2 executions | 2 executions |
| Direct SSR load, JavaScript disabled | 1 execution | 1 execution |
The source-level inputs do not change: the inline capture remains captured and the application FormData remains message=hello. I expect the second submission to reuse the first cache entry in every case.
The cache miss appears to come from React's $ACTION_* transport controls becoming part of the FormData cache argument:
- A freshly rendered inline action has
$ACTION_REF_*metadata containing newly encrypted bound captures, so its value changes after reload. - A file-level action has stable
$ACTION_ID_*metadata, which explains why the direct SSR case hits. - Client navigation initially submits only application fields, while the hydrated SSR form after reload also submits action controls, so even the file-level action misses.
- Native no-JavaScript submissions pass through
decodeAction(), which removes the action controls before invoking the cached function, so both directive forms hit.
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 25.5.0
Available memory (MB): 49152
Available CPU cores: 12
Binaries:
Node: 24.19.0
npm: 11.17.0
Yarn: N/A
pnpm: 10.34.5
Relevant Packages:
next: 16.3.0
eslint-config-next: N/A
react: 19.2.8
react-dom: 19.2.8
typescript: 5.9.3
Next.js Config:
output: N/A
Which area(s) are affected? (Select all that apply)
Use Cache, Server Actions
Which stage(s) are affected? (Select all that apply)
next dev (local), next build (local), next start (local)
Additional context
The same issue was reproduced on @vitejs/plugin-rsc demo in https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-rsc/examples/use-cache-callable and it appears the fix would require framework side cache key derivation to somehow exclude $ACTION_ related keys.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the reproduction at next-use-cache-form-reload and run pnpm test:dev and pnpm test:build. Trace the form-data handling around React's decodeAction() and framework cache-key derivation, especially $ACTION_* fields. Done means hydrated reload submissions reuse the cache entry for both inline and file-level actions across the listed scenarios.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, nextjs, react
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100