Skip to main content

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 or useEffect, wrapped in startTransition so 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 receives FormData. 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) (from react) returns [state, formAction, isPending]; the action then receives (prevState, formData). Use it to return validation errors.
  • useFormStatus() (from react-dom) gives pending in 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​

TaskHow
Show the new dataupdateTag(tag) or revalidatePath(path); revalidateTag(tag, 'max') refreshes in the background
Go to another pageredirect('/posts/1'), called outside try/catch
Return validation or server errorsReturn an object and read it with useActionState
Optimistic UIuseOptimistic

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, revalidatePath or refresh(), 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 Origin with Host (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 FormData instead of the session.
  • Calling redirect() inside try/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.

See Also​