Server Actions
A Server Action is an async function marked with 'use server' that
runs on the server but can be called from a component: passed to
<form action>, called from an event handler, or passed as a prop. Next.js
turns each action into a POST endpoint with a generated ID. React 19 calls
them Server Functions; "Server Action" is one used for a mutation. Because
anyone can call that endpoint, an action must check authorization and validate
its input itself.
Applies to: Next.js 16.4, React 19.3.
Details
Defining and Calling
'use server'at the top of a file makes every export of the file an action; this file can be imported from Client Components.'use server'at the start of a function body defines an inline action inside a Server Component.- Ways to call:
<form action={fn}>and<button formAction={fn}>, or from an event handler oruseEffect, wrapped instartTransitionso that the pending state works. - Arguments and the return value must be serializable.
- Actions are meant for mutations, not for fetching data: read data in Server Components.
Forms
- With
<form action={fn}>the function receivesFormData. The form works before JavaScript loads (progressive enhancement) when it is rendered by a Server Component; in a Client Component the submit waits for hydration. useActionState(action, initialState)(fromreact) returns[state, formAction, isPending]; the action then receives(prevState, formData). Use it to return validation errors.useFormStatus()(fromreact-dom) givespendingin a component rendered inside the<form>, such as a submit button.- To submit from code, call
formRef.current.requestSubmit(): it runs validation and the submit handlers like a real submit.
After a Mutation
| Task | How |
|---|---|
| Show the new data | updateTag(tag) or revalidatePath(path); revalidateTag(tag, 'max') refreshes in the background |
| Go to another page | redirect('/posts/1'), called outside try/catch |
| Return validation or server errors | Return an object and read it with useActionState |
| Optimistic UI | useOptimistic |
Example
An action with authorization, validation and revalidation
app/posts/actions.ts
'use server';
import { z } from 'zod';
import { updateTag } from 'next/cache';
import { redirect } from 'next/navigation';
import { verifySession } from '@/lib/dal';
import { db } from '@/lib/db';
const PostSchema = z.object({
title: z.string().min(1, 'Title is required'),
body: z.string().max(5000),
});
export type State = {
errors?: Record<string, string[] | undefined>;
values?: { title?: string; body?: string };
};
export async function createPost(
_prev: State,
formData: FormData
): Promise<State> {
const { userId } = await verifySession(); // the endpoint is public
const values = {
title: String(formData.get('title') ?? ''),
body: String(formData.get('body') ?? ''),
};
const result = PostSchema.safeParse(values);
if (!result.success) {
// Return the values too: React resets the form after the action
return { errors: z.flattenError(result.error).fieldErrors, values };
}
const post = await db.post.create({
data: { ...result.data, authorId: userId },
});
updateTag('posts');
redirect(`/posts/${post.id}`);
}
app/posts/new/post-form.tsx
'use client';
import { useActionState } from 'react';
import { createPost, type State } from '../actions';
export function PostForm() {
const [state, formAction, isPending] = useActionState(
createPost,
{} as State
);
return (
<form action={formAction}>
<input name="title" defaultValue={state.values?.title} />
{state.errors?.title && <p>{state.errors.title[0]}</p>}
<textarea name="body" defaultValue={state.values?.body} />
<button disabled={isPending}>Publish</button>
</form>
);
}
Follow-up Questions
- Is a Server Action secure because it is not exposed as a URL? No: it is a POST to the page with an action ID that can be found in the client bundle. Check the session and validate every argument.
- Server Action or Route Handler? An action for mutations from your own UI; a Route Handler for webhooks, other clients and non-POST methods. See Route Handlers.
- Server Action or Server Function? Until September 2024 React called every
'use server'function a Server Action. Then it introduced the broader name Server Function, because such a function can also be called outside a mutation, for example to read data from a Client Component. A Server Action is now a Server Function passed to<form action>or called inside a transition. - What happens to the page after an action? Next.js returns the updated RSC
Payload in the same response when the action calls
updateTag,revalidatePathorrefresh(), changes cookies or redirects, so there is no extra round trip.revalidateTag(tag, 'max')only marks the tag stale and does not re-render in the same response. - What does Next.js do to protect actions? It compares
OriginwithHost(against CSRF), encrypts action IDs and closure variables, removes unused actions from the client bundle and limits the body to 1 MB by default (experimental.serverActions.bodySizeLimit). None of it replaces your own auth and validation.
Common Mistakes
- Trusting the user ID or the role from
FormDatainstead of the session. - Calling
redirect()insidetry/catch: it works by throwing. - Fetching data with an action from a Client Component instead of reading it in a Server Component.
- Calling several actions with
Promise.all: they still run one after another. Do the parallel work inside one action.