Use when designing or reviewing React Server Functions / Next.js Server Actions for mutations: the 'use server' directive, function-to-POST endpoint semantics, form integration through action/formAction, React useActionState and useFormStatus, progressive enhancement, server-side validation, authentication, authorization, rate limiting, cache revalidation, redirect/refresh/updateTag behavior, bound arguments, and the security boundary that makes actions public HTTP endpoints despite function-like syntax. Covers Next.js App Router as the canonical implementation. Do NOT use for read-path data fetching with React Server Components (use server-components-design), broader serialization/directive mechanics (use client-server-boundary), externally consumed API contracts (use api-design), or form visual/interaction UX (use form-ux-architecture). Do NOT use for choose between SSR and SSG (use rendering-models). Do NOT use for debug React hook dependency arrays in a client form (use hooks-patterns).
Server Actions design is the discipline of using React Server Functions for App Router mutations without forgetting that the function-shaped API is still an HTTP boundary. A form or client transition serializes untrusted values, sends a POST to a generated server endpoint, executes privileged code server-side, then returns action state and optionally refreshed UI — so the practitioner must treat every argument as attacker-controlled and every visible read path as stale until revalidated or refreshed. The model exists because Server Actions reduce duplicated client/server mutation code, preserve form-first progressive enhancement, and integrate with the Next.js cache; but that same convenience can hide authentication, authorization, validation, and cache-invalidation mistakes behind ordinary-looking function calls. It is not Server Component read-path design, a public API contract, the whole client/server serialization model, form visual UX, or general hook discipline: server-components-design owns reads, client-server-boundary owns serialization/directive mechanics, api-design owns stable external HTTP contracts, security-fundamentals checks trust boundaries, form-ux-architecture owns the user-facing form experience, and hooks-patterns owns general Client Component hook rules. The one-line analogy: a Server Action is a privileged back-office operation triggered by a normal form; the form is convenient, but the office still checks identity, authority, and paperwork. The common misconception to correct is that a Server Action is private merely because the client imports it like a function — it is a reachable POST endpoint with framework protections that do not replace authorization or validation.
Coverage
The design discipline for React Server Functions and Next.js Server Actions used as mutations: where to place 'use server', how actions become POST endpoints, how forms invoke actions through action and formAction, how useActionState and useFormStatus expose state and pending UI, how progressive enhancement constrains the design, how to validate and authorize inputs, how to choose cache revalidation primitives, and when to use a route handler or public API instead.
Use the current vocabulary deliberately:
Term
Meaning
Server Function
React's broader async server-executed function primitive.
Server Action
A Server Function used in an action or mutation context, commonly through forms or transitions.
'use server'
The directive that marks an async function or module's exports as server-executed.
Action endpoint
The generated POST endpoint the framework uses to invoke the server function.
Philosophy of the skill
Before Server Actions, a browser mutation usually required two parallel structures: client code that called fetch('/api/foo', { method: 'POST', body: ... }) and a server route handler that parsed the request, validated it, authorized it, executed the mutation, and serialized a response. The two sides had to agree on a wire format and failure shape.
Server Actions collapse that into one server-side declaration that the UI can bind to a form. This removes boilerplate and drift. It also makes the dangerous part easier to miss: the collapse is syntactic, not semantic. The server still receives serialized input over HTTP from a caller that may not be your UI. Treat the function as a public endpoint disguised as a function.
The second principle is progressive enhancement. A form wired with <form action={serverAction}> can submit before client JavaScript is loaded and can still be enhanced after hydration. Designing the mutation around click handlers, local-only state, or event-only invocation throws away one of the major reasons to use actions.
The 'use server' Contract
Use one of these declaration shapes:
Shape
Use when
Constraint
Module-level directive
A shared actions file is imported by Server and Client Components.
All exports in the file are server functions.
Inline directive inside a Server Component
The action needs server-side closure context from the component render.
It can be passed to a form or button from that Server Component.
Imported action in a Client Component
The Client Component needs to invoke the action through a form, button, transition, or event.
The action must live in a module-level 'use server' file.
// app/actions/comments.ts
'use server'
export async function createComment(formData: FormData) {
// runs on the server
}
// app/posts/[id]/page.tsx - Server Component
export default async function PostPage() {
async function addComment(formData: FormData) {
'use server'
// can close over server-side render state
}
return <form action={addComment}>...</form>
}
Arguments and return values must be serializable by React. Treat the apparent TypeScript signature as developer ergonomics, not runtime validation. The browser can submit different bytes than the UI would produce.
Form-First Mutation Pattern
The canonical action consumes FormData, validates it on the server, checks the session and permissions, mutates, then updates the read path.
Hidden inputs are user-controlled. Use them for convenience, not authority. The action decides whether the authenticated user may mutate the referenced resource.
useActionState and useFormStatus
useActionState wraps an action and gives the client the latest returned state plus a form action to pass into <form action={...}>.
The server function used with useActionState accepts the previous state first and the submitted FormData second. It can delegate to the plain form action if you want one mutation implementation:
When a function is wrapped by useActionState and used as a form action, React passes the previous state as the first argument and the submitted FormData as the next argument. Design the server function signature for that shape when using the hook.
useFormStatus reads status from a descendant of the nearest parent form.
It does not observe a form rendered in the same component that calls the hook. Put the submit button in a child component.
Cache And Navigation After Writes
A mutation that changes data visible to Server Components must update the relevant cached read path or router state.
Primitive
Use when
Notes
revalidatePath(path, type?)
A specific page, layout, or route handler cache should be invalidated.
In Server Functions it can update the currently viewed affected path immediately; dynamic route patterns need type.
revalidateTag(tag, 'max')
Tagged cached data can be stale while fresh data loads in the background.
Can be called in Server Functions and Route Handlers; immediate-expiration form is deprecated unless using explicit advanced options.
updateTag(tag)
A Server Action needs read-your-own-writes for tagged cached data.
Server Actions only; immediately expires the tag so the next request waits for fresh data.
refresh()
The current client router should refresh from inside a Server Action.
Server Actions only; use when router state must be refreshed and a path/tag invalidation is not the right primitive.
redirect(path)
Successful mutation should navigate somewhere else.
Throws a framework-handled control-flow exception; call cache updates first and avoid swallowing it in catch.
If the action mutates database state and returns ok: true but never revalidates, the UI can continue showing stale data. Choose the smallest primitive that makes the user's next read correct.
Bound values are not a security boundary. Use them to avoid hidden inputs or simplify call sites, but never bind secrets and never skip authorization. Next.js can encrypt closed-over variables for inline actions, but the docs warn not to rely on encryption alone to prevent sensitive exposure. The action must still re-read current server truth and authorize the operation when invoked.
Security Discipline
Concern
Required design choice
Public endpoint semantics
Treat every exported action as a public HTTP endpoint, even with encrypted/non-deterministic action IDs.
Authentication
Read the current session server-side inside the action or its data access layer.
Authorization
Check permission against the target resource at the moment of mutation.
Input validation
Parse FormData or serialized arguments with a runtime schema before mutating.
Hidden or bound values
Treat them as attacker-controlled references, not proof of authority.
CSRF
Rely on the framework same-origin and POST-only protections only within their documented assumptions; configure serverActions.allowedOrigins narrowly when proxies require it.
Body size
Know the default 1 MB request limit and configure serverActions.bodySizeLimit only for justified larger forms.
Rate limiting
Add per-action throttles for expensive, anonymous, or abuse-prone operations.
Return values
Return only data the client is allowed to see, preferably a minimal result object.
Use route handlers or api-design instead when the caller is not this App Router UI, when clients need stable URL/method/status semantics, when third parties need documentation, or when a webhook/mobile/server-to-server consumer must call the mutation.
Common Anti-Patterns
Anti-pattern
Why it fails
Correction
Trusting a client-supplied userId, role, or orgId
The browser controls submitted values.
Derive actor identity from the server session and authorize against server truth.
Validating only with TypeScript or client UI
Types and disabled controls do not run on hostile requests.
Parse at the action boundary with a runtime schema.
Using event handlers for normal forms
Loses progressive enhancement and pre-hydration submission.
Prefer <form action={serverAction}> and formAction for submit buttons.
Forgetting revalidation
Server state changes while Server Component UI stays stale.
Call the appropriate path/tag/router primitive after the write.
Catching redirect accidentally
redirect throws; catch blocks can swallow navigation.
Call it outside try/catch, or rethrow framework control-flow errors.
Returning raw database records
Internal fields can cross to the client.
Return a minimal serializable result or DTO.
Treating action IDs as authorization
Secure IDs reduce accidental exposure but do not prove caller rights.
Authenticate and authorize every mutation.
Using Server Actions as external APIs
Generated endpoints are not stable public contracts.
Use route handlers / REST / GraphQL and api-design.
Verification
After applying this skill, verify:
Every action checks authentication before privileged work.
Every action authorizes the actor against the target resource.
Every argument from FormData, hidden inputs, bound arguments, URL params, or serialized client calls is parsed server-side.
Every mutation updates visible read paths with revalidatePath, revalidateTag, updateTag, refresh, or a justified redirect.
Forms that can be native forms use action or formAction, not a JS-only click handler.
useActionState actions have the correct previous-state plus payload signature.
useFormStatus is called in a descendant of the form it observes.
No secret or authority-bearing value is hidden, bound, or closed over as the only protection.
Expensive or anonymous actions have rate limiting.
Return values expose only client-safe data.
Externally consumed contracts use route handlers or API design instead of generated action endpoints.
Grounding Sources
React docs - 'use server'. The React directive contract for Server Functions.
React docs - useActionState. The hook for action state and pending status.
React docs - useFormStatus. The hook for nearest-parent form status.
Next.js docs - Mutating Data. Current App Router Server Functions / Server Actions overview.
Next.js docs - use server. Next.js directive usage and security considerations.
Next.js docs - Data Security. Public endpoint semantics, authorization, rate limiting, closures, encryption, and audit guidance.
Next.js docs - serverActions. allowedOrigins and bodySizeLimit configuration.
That skill owns data fetching during render; this skill owns client-triggered mutations.
Explaining the entire 'use client' / 'use server' serialization model
client-server-boundary
This skill applies the boundary to actions, not all boundary mechanics.
Designing a public REST, GraphQL, mobile, webhook, or third-party API
api-design
Server Actions are internal UI endpoints, not stable external contracts.
Designing validation layout, field messages, focus, accessibility, or microcopy
form-ux-architecture
This skill owns the server execution model, not form presentation.
Debugging general React hook rules
hooks-patterns
This skill covers action-specific hooks only.
Skill Graph context
Classification
Subject: frontend-engineering
Public: true
Domain: engineering/frontend
Scope: Teaching the portable mutation-design discipline for React Server Functions and Next.js Server Actions: when a 'use server' function becomes an invokable POST endpoint, how form action/formAction integration preserves progressive enhancement, how useActionState and useFormStatus report mutation state, how to validate and authorize untrusted arguments, how to revalidate or refresh UI after writes, and how to choose between an in-app action and a public API contract. Applies to Next.js App Router mutations and form submissions. Excludes read-path Server Component fetching (server-components-design), general client/server serialization mechanics (client-server-boundary), public REST/GraphQL/mobile/third-party API design (api-design), and visual form UX or accessibility details (form-ux-architecture).
When to use
design a create-comment form using a Server Action and useActionState so it works without JavaScript and reports server-side validation errors
decide whether a delete button should call a Server Action or an API route
audit a Server Action for missing authorization even though it looks like a normal imported function
design the cache revalidation strategy for a mutation that changes multiple cached routes
review whether bound action arguments are safe for this edit form
Triggers: how do I submit a form to the server, do I need an API route for this mutation, how do I call a server function from a button, why is my Server Action exposed as an endpoint, useActionState vs useFormState, how do I revalidate after mutation, can Server Actions run in event handlers, how do I use updateTag after a Server Action
Not for
design a Server Component that reads data on render (use server-components-design)
design a public REST API consumed by mobile clients or third parties (use api-design)
choose between SSR and SSG (use rendering-models)
design the visual UX and accessibility of a form's validation states (use form-ux-architecture)
explain the whole use client serialization boundary (use client-server-boundary)
debug React hook dependency arrays in a client form (use hooks-patterns)
Analogy: A Server Action is like a service-counter form wired straight to the back office: the customer fills out normal paperwork, the clerk executes privileged work behind the counter, and the office must still check identity, authority, and the paperwork before changing records.
Server Actions, Server Functions, use server, form action attribute, formAction, useActionState, useFormStatus, revalidatePath, updateTag, Next.js mutation
1---2name: server-actions-design3description: Use when designing or reviewing React Server Functions / Next.js Server Actions for mutations: the 'use server' directive, function-to-POST endpoint semantics, form integration through action/formAction, React useActionState and useFormStatus, progressive enhancement, server-side validation, authentication, authorization, rate limiting, cache revalidation, redirect/refresh/updateTag behavior, bound arguments, and the security boundary that makes actions public HTTP endpoints despite function-like syntax. Covers Next.js App Router as the canonical implementation. Do NOT use for read-path data fetching with React Server Components (use server-components-design), broader serialization/directive mechanics (use client-server-boundary), externally consumed API contracts (use api-design), or form visual/interaction UX (use form-ux-architecture). Do NOT use for choose between SSR and SSG (use rendering-models). Do NOT use for debug React hook dependency arrays in a client form (use hooks-patterns).4license: MIT5---6# Server Actions Design78## Concept of the skill910Server Actions design is the discipline of using React Server Functions for App Router mutations without forgetting that the function-shaped API is still an HTTP boundary. A form or client transition serializes untrusted values, sends a POST to a generated server endpoint, executes privileged code server-side, then returns action state and optionally refreshed UI — so the practitioner must treat every argument as attacker-controlled and every visible read path as stale until revalidated or refreshed. The model exists because Server Actions reduce duplicated client/server mutation code, preserve form-first progressive enhancement, and integrate with the Next.js cache; but that same convenience can hide authentication, authorization, validation, and cache-invalidation mistakes behind ordinary-looking function calls. It is **not** Server Component read-path design, a public API contract, the whole client/server serialization model, form visual UX, or general hook discipline: `server-components-design` owns reads, `client-server-boundary` owns serialization/directive mechanics, `api-design` owns stable external HTTP contracts, `security-fundamentals` checks trust boundaries, `form-ux-architecture` owns the user-facing form experience, and `hooks-patterns` owns general Client Component hook rules. The one-line analogy: a Server Action is a privileged back-office operation triggered by a normal form; the form is convenient, but the office still checks identity, authority, and paperwork. The common misconception to correct is that a Server Action is private merely because the client imports it like a function — it is a reachable POST endpoint with framework protections that do not replace authorization or validation.1112## Coverage1314The design discipline for React Server Functions and Next.js Server Actions used as mutations: where to place `'use server'`, how actions become POST endpoints, how forms invoke actions through `action` and `formAction`, how `useActionState` and `useFormStatus` expose state and pending UI, how progressive enhancement constrains the design, how to validate and authorize inputs, how to choose cache revalidation primitives, and when to use a route handler or public API instead.1516Use the current vocabulary deliberately:1718| Term | Meaning |19|---|---|20| Server Function | React's broader async server-executed function primitive. |21| Server Action | A Server Function used in an action or mutation context, commonly through forms or transitions. |22| `'use server'` | The directive that marks an async function or module's exports as server-executed. |23| Action endpoint | The generated POST endpoint the framework uses to invoke the server function. |2425## Philosophy of the skill2627Before Server Actions, a browser mutation usually required two parallel structures: client code that called `fetch('/api/foo', { method: 'POST', body: ... })` and a server route handler that parsed the request, validated it, authorized it, executed the mutation, and serialized a response. The two sides had to agree on a wire format and failure shape.2829Server Actions collapse that into one server-side declaration that the UI can bind to a form. This removes boilerplate and drift. It also makes the dangerous part easier to miss: the collapse is syntactic, not semantic. The server still receives serialized input over HTTP from a caller that may not be your UI. Treat the function as a public endpoint disguised as a function.3031The second principle is progressive enhancement. A form wired with `<form action={serverAction}>` can submit before client JavaScript is loaded and can still be enhanced after hydration. Designing the mutation around click handlers, local-only state, or event-only invocation throws away one of the major reasons to use actions.3233## The `'use server'` Contract3435Use one of these declaration shapes:3637| Shape | Use when | Constraint |38|---|---|---|39| Module-level directive | A shared actions file is imported by Server and Client Components. | All exports in the file are server functions. |40| Inline directive inside a Server Component | The action needs server-side closure context from the component render. | It can be passed to a form or button from that Server Component. |41| Imported action in a Client Component | The Client Component needs to invoke the action through a form, button, transition, or event. | The action must live in a module-level `'use server'` file. |4243```ts44// app/actions/comments.ts45'use server'4647export async function createComment(formData: FormData) {48 // runs on the server49}50```5152```tsx53// app/posts/[id]/page.tsx - Server Component54export default async function PostPage() {55 async function addComment(formData: FormData) {56 'use server'57 // can close over server-side render state58 }5960 return <form action={addComment}>...</form>61}62```6364Arguments and return values must be serializable by React. Treat the apparent TypeScript signature as developer ergonomics, not runtime validation. The browser can submit different bytes than the UI would produce.6566## Form-First Mutation Pattern6768The canonical action consumes `FormData`, validates it on the server, checks the session and permissions, mutates, then updates the read path.6970```tsx71'use server'7273import { revalidatePath } from 'next/cache'74import { z } from 'zod'75import { auth } from '@/auth'76import { db } from '@/db'7778const CommentInput = z.object({79 postId: z.string().uuid(),80 body: z.string().min(1).max(1000),81})8283export async function addComment(formData: FormData) {84 const session = await auth()85 if (!session?.user) {86 return { ok: false, error: 'Unauthorized' }87 }8889 const parsed = CommentInput.safeParse({90 postId: formData.get('postId'),91 body: formData.get('body'),92 })9394 if (!parsed.success) {95 return { ok: false, fieldErrors: parsed.error.flatten().fieldErrors }96 }9798 const canComment = await db.post.canComment({99 postId: parsed.data.postId,100 userId: session.user.id,101 })102103 if (!canComment) {104 return { ok: false, error: 'Forbidden' }105 }106107 await db.comment.create({108 data: {109 postId: parsed.data.postId,110 body: parsed.data.body,111 authorId: session.user.id,112 },113 })114115 revalidatePath(`/posts/${parsed.data.postId}`)116 return { ok: true }117}118```119120```tsx121<form action={addComment}>122 <input type="hidden" name="postId" value={postId} />123 <textarea name="body" required />124 <button type="submit">Post</button>125</form>126```127128Hidden inputs are user-controlled. Use them for convenience, not authority. The action decides whether the authenticated user may mutate the referenced resource.129130## `useActionState` and `useFormStatus`131132`useActionState` wraps an action and gives the client the latest returned state plus a form action to pass into `<form action={...}>`.133134The server function used with `useActionState` accepts the previous state first and the submitted `FormData` second. It can delegate to the plain form action if you want one mutation implementation:135136```ts137'use server'138139type CommentState =140 | { ok: true }141 | { ok: false; error?: string; fieldErrors?: Record<string, string[]> }142143export async function addCommentWithState(144 _previousState: CommentState,145 formData: FormData,146): Promise<CommentState> {147 return addComment(formData)148}149```150151```tsx152'use client'153154import { useActionState } from 'react'155import { addCommentWithState } from '@/app/actions/comments'156157export function CommentForm({ postId }: { postId: string }) {158 const [state, formAction, isPending] = useActionState(addCommentWithState, { ok: true })159160 return (161 <form action={formAction}>162 <input type="hidden" name="postId" value={postId} />163 <textarea name="body" />164 {!state.ok && <p>{state.error ?? state.fieldErrors?.body?.[0]}</p>}165 <button disabled={isPending}>{isPending ? 'Posting...' : 'Post'}</button>166 </form>167 )168}169```170171When a function is wrapped by `useActionState` and used as a form action, React passes the previous state as the first argument and the submitted `FormData` as the next argument. Design the server function signature for that shape when using the hook.172173`useFormStatus` reads status from a descendant of the nearest parent form.174175```tsx176'use client'177178import { useFormStatus } from 'react-dom'179180export function SubmitButton() {181 const { pending } = useFormStatus()182 return <button disabled={pending}>{pending ? 'Saving...' : 'Save'}</button>183}184```185186It does not observe a form rendered in the same component that calls the hook. Put the submit button in a child component.187188## Cache And Navigation After Writes189190A mutation that changes data visible to Server Components must update the relevant cached read path or router state.191192| Primitive | Use when | Notes |193|---|---|---|194| `revalidatePath(path, type?)` | A specific page, layout, or route handler cache should be invalidated. | In Server Functions it can update the currently viewed affected path immediately; dynamic route patterns need `type`. |195| `revalidateTag(tag, 'max')` | Tagged cached data can be stale while fresh data loads in the background. | Can be called in Server Functions and Route Handlers; immediate-expiration form is deprecated unless using explicit advanced options. |196| `updateTag(tag)` | A Server Action needs read-your-own-writes for tagged cached data. | Server Actions only; immediately expires the tag so the next request waits for fresh data. |197| `refresh()` | The current client router should refresh from inside a Server Action. | Server Actions only; use when router state must be refreshed and a path/tag invalidation is not the right primitive. |198| `redirect(path)` | Successful mutation should navigate somewhere else. | Throws a framework-handled control-flow exception; call cache updates first and avoid swallowing it in `catch`. |199200If the action mutates database state and returns `ok: true` but never revalidates, the UI can continue showing stale data. Choose the smallest primitive that makes the user's next read correct.201202## Bound Arguments And Closures203204`bind` can pre-fill action arguments:205206```tsx207const deletePost = deletePostAction.bind(null, post.id)208209return (210 <form action={deletePost}>211 <button type="submit">Delete</button>212 </form>213)214```215216Bound values are not a security boundary. Use them to avoid hidden inputs or simplify call sites, but never bind secrets and never skip authorization. Next.js can encrypt closed-over variables for inline actions, but the docs warn not to rely on encryption alone to prevent sensitive exposure. The action must still re-read current server truth and authorize the operation when invoked.217218## Security Discipline219220| Concern | Required design choice |221|---|---|222| Public endpoint semantics | Treat every exported action as a public HTTP endpoint, even with encrypted/non-deterministic action IDs. |223| Authentication | Read the current session server-side inside the action or its data access layer. |224| Authorization | Check permission against the target resource at the moment of mutation. |225| Input validation | Parse `FormData` or serialized arguments with a runtime schema before mutating. |226| Hidden or bound values | Treat them as attacker-controlled references, not proof of authority. |227| CSRF | Rely on the framework same-origin and POST-only protections only within their documented assumptions; configure `serverActions.allowedOrigins` narrowly when proxies require it. |228| Body size | Know the default 1 MB request limit and configure `serverActions.bodySizeLimit` only for justified larger forms. |229| Rate limiting | Add per-action throttles for expensive, anonymous, or abuse-prone operations. |230| Return values | Return only data the client is allowed to see, preferably a minimal result object. |231232Use route handlers or `api-design` instead when the caller is not this App Router UI, when clients need stable URL/method/status semantics, when third parties need documentation, or when a webhook/mobile/server-to-server consumer must call the mutation.233234## Common Anti-Patterns235236| Anti-pattern | Why it fails | Correction |237|---|---|---|238| Trusting a client-supplied `userId`, `role`, or `orgId` | The browser controls submitted values. | Derive actor identity from the server session and authorize against server truth. |239| Validating only with TypeScript or client UI | Types and disabled controls do not run on hostile requests. | Parse at the action boundary with a runtime schema. |240| Using event handlers for normal forms | Loses progressive enhancement and pre-hydration submission. | Prefer `<form action={serverAction}>` and `formAction` for submit buttons. |241| Forgetting revalidation | Server state changes while Server Component UI stays stale. | Call the appropriate path/tag/router primitive after the write. |242| Catching `redirect` accidentally | `redirect` throws; catch blocks can swallow navigation. | Call it outside `try/catch`, or rethrow framework control-flow errors. |243| Returning raw database records | Internal fields can cross to the client. | Return a minimal serializable result or DTO. |244| Treating action IDs as authorization | Secure IDs reduce accidental exposure but do not prove caller rights. | Authenticate and authorize every mutation. |245| Using Server Actions as external APIs | Generated endpoints are not stable public contracts. | Use route handlers / REST / GraphQL and `api-design`. |246247## Verification248249After applying this skill, verify:250251- [ ] Every action checks authentication before privileged work.252- [ ] Every action authorizes the actor against the target resource.253- [ ] Every argument from `FormData`, hidden inputs, bound arguments, URL params, or serialized client calls is parsed server-side.254- [ ] Every mutation updates visible read paths with `revalidatePath`, `revalidateTag`, `updateTag`, `refresh`, or a justified redirect.255- [ ] Forms that can be native forms use `action` or `formAction`, not a JS-only click handler.256- [ ] `useActionState` actions have the correct previous-state plus payload signature.257- [ ] `useFormStatus` is called in a descendant of the form it observes.258- [ ] No secret or authority-bearing value is hidden, bound, or closed over as the only protection.259- [ ] Expensive or anonymous actions have rate limiting.260- [ ] Return values expose only client-safe data.261- [ ] Externally consumed contracts use route handlers or API design instead of generated action endpoints.262263## Grounding Sources264265- React docs - [`'use server'`](https://react.dev/reference/rsc/use-server). The React directive contract for Server Functions.266- React docs - [`useActionState`](https://react.dev/reference/react/useActionState). The hook for action state and pending status.267- React docs - [`useFormStatus`](https://react.dev/reference/react-dom/hooks/useFormStatus). The hook for nearest-parent form status.268- Next.js docs - [Mutating Data](https://nextjs.org/docs/app/getting-started/mutating-data). Current App Router Server Functions / Server Actions overview.269- Next.js docs - [`use server`](https://nextjs.org/docs/app/api-reference/directives/use-server). Next.js directive usage and security considerations.270- Next.js docs - [Data Security](https://nextjs.org/docs/app/guides/data-security). Public endpoint semantics, authorization, rate limiting, closures, encryption, and audit guidance.271- Next.js docs - [`serverActions`](https://nextjs.org/docs/app/api-reference/config/next-config-js/serverActions). `allowedOrigins` and `bodySizeLimit` configuration.272- Next.js docs - [`revalidatePath`](https://nextjs.org/docs/app/api-reference/functions/revalidatePath), [`revalidateTag`](https://nextjs.org/docs/app/api-reference/functions/revalidateTag), [`updateTag`](https://nextjs.org/docs/app/api-reference/functions/updateTag), [`refresh`](https://nextjs.org/docs/app/api-reference/functions/refresh), and [`redirect`](https://nextjs.org/docs/app/api-reference/functions/redirect). Cache, router, and navigation primitives after writes.273274## Do NOT Use When275276| Instead of this skill | Use | Why |277|---|---|---|278| Designing the Server Component read path | `server-components-design` | That skill owns data fetching during render; this skill owns client-triggered mutations. |279| Explaining the entire `'use client'` / `'use server'` serialization model | `client-server-boundary` | This skill applies the boundary to actions, not all boundary mechanics. |280| Designing a public REST, GraphQL, mobile, webhook, or third-party API | `api-design` | Server Actions are internal UI endpoints, not stable external contracts. |281| Designing validation layout, field messages, focus, accessibility, or microcopy | `form-ux-architecture` | This skill owns the server execution model, not form presentation. |282| Debugging general React hook rules | `hooks-patterns` | This skill covers action-specific hooks only. |283284## Skill Graph context285286<!-- skill-graph-context:start (generated — do not edit by hand) -->287288**Classification**289- Subject: `frontend-engineering`290- Public: `true`291- Domain: `engineering/frontend`292- Scope: Teaching the portable mutation-design discipline for React Server Functions and Next.js Server Actions: when a 'use server' function becomes an invokable POST endpoint, how form action/formAction integration preserves progressive enhancement, how useActionState and useFormStatus report mutation state, how to validate and authorize untrusted arguments, how to revalidate or refresh UI after writes, and how to choose between an in-app action and a public API contract. Applies to Next.js App Router mutations and form submissions. Excludes read-path Server Component fetching (server-components-design), general client/server serialization mechanics (client-server-boundary), public REST/GraphQL/mobile/third-party API design (api-design), and visual form UX or accessibility details (form-ux-architecture).293294**When to use**295- design a create-comment form using a Server Action and useActionState so it works without JavaScript and reports server-side validation errors296- decide whether a delete button should call a Server Action or an API route297- audit a Server Action for missing authorization even though it looks like a normal imported function298- design the cache revalidation strategy for a mutation that changes multiple cached routes299- review whether bound action arguments are safe for this edit form300- Triggers: `how do I submit a form to the server`, `do I need an API route for this mutation`, `how do I call a server function from a button`, `why is my Server Action exposed as an endpoint`, `useActionState vs useFormState`, `how do I revalidate after mutation`, `can Server Actions run in event handlers`, `how do I use updateTag after a Server Action`301302**Not for**303- design a Server Component that reads data on render (use server-components-design)304- design a public REST API consumed by mobile clients or third parties (use api-design)305- choose between SSR and SSG (use rendering-models)306- design the visual UX and accessibility of a form's validation states (use form-ux-architecture)307- explain the whole use client serialization boundary (use client-server-boundary)308- debug React hook dependency arrays in a client form (use hooks-patterns)309- Owned by `server-components-design`: the read path310311**Related skills**312- Verify with: `client-server-boundary`, `server-components-design`, `api-design`, `security-fundamentals`, `form-ux-architecture`, `hooks-patterns`313- Related: `server-components-design`, `client-server-boundary`, `form-ux-architecture`, `api-design`, `hooks-patterns`, `security-fundamentals`, `http-semantics`314315**Concept**316- Mental model: |317- Purpose: |318- Boundary: |319- Analogy: A Server Action is like a service-counter form wired straight to the back office: the customer fills out normal paperwork, the clerk executes privileged work behind the counter, and the office must still check identity, authority, and the paperwork before changing records.320- Common misconception: |321322**Grounding**323- Mode: `universal`324- Truth sources: `https://nextjs.org/docs/app/getting-started/mutating-data`, `https://nextjs.org/docs/app/api-reference/directives/use-server`, `https://nextjs.org/docs/app/guides/data-security`, `https://nextjs.org/docs/app/api-reference/config/next-config-js/serverActions`, `https://nextjs.org/docs/app/api-reference/functions/revalidatePath`, `https://nextjs.org/docs/app/api-reference/functions/revalidateTag`, `https://nextjs.org/docs/app/api-reference/functions/updateTag`, `https://nextjs.org/docs/app/api-reference/functions/refresh`, `https://react.dev/reference/react/useActionState`, `https://react.dev/reference/react-dom/hooks/useFormStatus`325326**Keywords**327- `Server Actions`, `Server Functions`, `use server`, `form action attribute`, `formAction`, `useActionState`, `useFormStatus`, `revalidatePath`, `updateTag`, `Next.js mutation`328329<!-- skill-graph-context:end -->
Run npx skillmds@latest add jacob-balslev/server-actions-design in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Use when designing or reviewing React Server Functions / Next.js Server Actions for mutations: the 'use server' directive, function-to-POST endpoint semantics, form integration through action/formAction, React useActionState and useFormStatus, progressive enhancement, server-side validation, authentication, authorization, rate limiting, cache revalidation, redirect/refresh/updateTag behavior, bound arguments, and the security boundary that makes actions public HTTP endpoints despite function-like syntax. Covers Next.js App Router as the canonical implementation. Do NOT use for read-path data fetching with React Server Components (use server-components-design), broader serialization/directive mechanics (use client-server-boundary), externally consumed API contracts (use api-design), or form visual/interaction UX (use form-ux-architecture). Do NOT use for choose between SSR and SSG (use rendering-models). Do NOT use for debug React hook dependency arrays in a client form (use hooks-patterns). It is listed under Web & Frontend on SkillMD.
This skill has not completed SkillMD's automated safety review yet. Capability flags: makes network calls, reads secrets. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free. This skill is licensed under MIT.
jacob-balslev (@jacob-balslev) published this skill. Their other Agent Skills are listed on their SkillMD profile.