Server Actions
Priority: P1 (HIGH)
[!WARNING] If the project uses the
pages/directory instead of the App Router, ignore this skill entirely.
Handle form submissions and mutations without creating API endpoints.
Implementation Guidelines
-
Directive: Always start the file or function with
'use server'. AccessformData.get('title')for typed form fields. Export async functions for mutations. -
Form Handling: Use the
actionprop of<form>to trigger actions viaaction={createPost}. UseuseFormStatus()forpendingstates —disabled={pending}on buttons. UseuseActionState(React 19/Next.js 15) foraction={action}form state with<form action={action}>. -
Data Refresh: Trigger UI updates using
revalidatePath('/')orrevalidateTag('tag-name')after a successful mutation. -
Interactivity: For non-form triggers, invoke actions using the
useTransitionhook to handle loading UI and prevent the page from blocking. -
Optimistic Updates: Use
useOptimisticto show the expected UI state immediately before the server confirms the mutation. -
Security: Sanitize all inputs from
FormData. Perform auth checks inside every action (await auth()). Limit file uploads by size and MIME type. -
Form:
<form action={createPost}>(Progressive enhancements work without JS). -
Event Handler:
onClick={() => createPost(data)}. -
Pending State: Use
useFormStatushook (must be inside a component rendered within the form).
P1: Operational Standard
1. Secure & Validate
Always validate inputs with z.object({ schema and safeParse before processing. Check authorization within the action. See Secure Action Example.
2. Pending States
Use useActionState (React 19/Next.js 15+) for state handling and useFormStatus for button loading states.
Constraints
- Closures: Avoid defining actions inside components to prevent hidden closure encryption overhead and serialization bugs.
- Redirection: Use
redirect()for success navigation; it throws an error that Next.js catches to handle the redirect.
Anti-Patterns
- No unvalidated Server Action inputs: Always validate with Zod before processing.
- No skipped auth checks: Verify session/user inside every action, not just middleware.
- No actions defined inside components: Define in
actions.tsto avoid closure bugs. - No
redirect()in try/catch:redirect()throws; catching it suppresses the redirect.