Next.js / Editorial solution guide
Diagnose Server Action authority and uncertain write replay
Verify the action’s own input and policy boundary and reconcile a committed outcome before retrying.
Read-only knowledge base · No public comments
Recognize the symptoms
A direct action changes a course the page hid, a field becomes unexpectedly blank, or a retry duplicates an effect after the client reports failure. A disabled button and a protected layout do not establish action authorization. A missing response does not establish that a source write never committed.
Understand the likely causes
- Possible causes include trusting hidden or bound input, validating only in client code, stringifying a file-like FormData value, merging omission with null, missing object policy, or confusing cache/redirect failure with failed commit. Duplicate transport attempts need logical operation identity; an object ID alone cannot distinguish a replay from a new edit.
Treat the action as an independently callable server endpoint
Identify the actual exported action, current principal resolution and domain service method. Do not assume a prior page render or Proxy check still proves authority at mutation time. Validate every caller-controlled value, including hidden IDs, bound parameters, expected revisions and request keys. FormData values can be text, file-like data or null, and a patch must preserve explicit member presence. Test a well-formed but unauthorized transition separately from malformed input. Keep exact safe rejection categories and a service spy proving no protected effect occurred after denial. Do not log credentials or full private form payloads while reproducing the issue.
Separate validation, commit, invalidation and navigation
Build one safe operation timeline: input accepted, authority established, source effect committed, result recorded, cache updated and response/redirect delivered. A failure in a later stage does not undo the earlier commit. Framework redirect uses control flow and should not be swallowed by a catch-all that reports generic mutation failure. A revalidation problem can leave stale UI while authoritative data is already new. Before retrying, read the operation or object outcome through the domain’s current-scope contract. Do not infer rollback from a 500, failed browser transition or missing action result.
Make the same logical intent return its recorded result
The local map returns the recorded synthetic result for a repeated operation and leaves its effect count unchanged. It illustrates a committed operation whose confirmation may need reconciliation. It does not implement principal scoping, canonical fingerprint conflict detection, shared atomic storage, retention or crash recovery; a real protocol needs all required pieces. A new edit must receive a new operation identity even for the same course. Expected revision addresses conflicting new edits, while the operation key addresses retransmission. Keep these roles distinct and verify concurrent duplicates and lost-response cases in an authorized isolated source integration.
const outcomes=new Map();let effects=0;
function attempt(id) {
if(outcomes.has(id))return outcomes.get(id);
const result={revision:++effects};outcomes.set(id,result);return result;
}
const first=attempt("rename-17");const replay=attempt("rename-17");
console.assert(first===replay && effects===1);
The same local result is returned with one effect. No real action response is sent or lost, and no durability is established.
Repair policy and replay before cosmetic success feedback
Move validation and current object authority inside the action/shared use case before source effects. Return canonical fields or stable expected problems for the client’s actual form-state signature. Choose tag/path freshness semantics after confirmed commit and keep them separate from operation replay. A pending UI can prevent an accidental double click but cannot replace durable uniqueness. This guide executes only the map model; it does not submit FormData, authenticate, invoke an action, write a database or revalidate a route. Verify the real repair with safe denied and allowed identities plus explicit source spies and recovery fixtures.
Verify the fix
Test invalid types, blank text, unknown sensitive fields, denied object scope, expected conflict, first success, identical replay and commit before lost response. Assert exact public result and zero protected writes for rejection. Verify cache/navigation recovery separately and preserve unresolved outcomes honestly.
Keep it from recurring
- Share domain authorization across actions and handlers, keep typed runtime parsing and patch presence explicit, and retain scoped operation results for the promised replay window. Test form-state integration, redirects and invalidation with the selected version/mode. Keep private data out of action results, metadata and diagnostics.