Next.js

Next.js Server Actions👨‍💻

Server Actions are async functions that run on the server and can be called directly from your React components. They replaced the old pattern of wiring up API routes for every mutation. Instead of writing a POST /api/todos route handler, creating a fetch wrapper, and managing loading states manually, you define a function with 'use server' and pass it to a <form action>. The framework handles the network request, serialization, and progressive enhancement for you.

The mental model: a Server Action is an RPC endpoint that Next.js generates automatically. When a user submits a form or clicks a button, the framework serializes the arguments, sends a POST request to the server, executes your function, and streams back the result. Because actions integrate with React's transition system, the UI stays responsive during the round trip. And because they work with the native <form> element, forms function even before JavaScript loads.

Key Takeaways

  • 1Server Actions are async functions marked with `'use server'` — either inline inside a Server Component or at the top of a dedicated file for use in Client Components
  • 2When passed to a `<form action>`, actions receive `FormData` automatically and support progressive enhancement (the form works without JavaScript)
  • 3Actions use the POST method under the hood — Next.js generates unique endpoints and handles CSRF protection automatically
  • 4After a mutation, call `revalidatePath()` or `revalidateTag()` to invalidate cached data so the UI reflects the change on the next render
  • 5`useActionState` from React gives you the action's return value (for validation errors), a wrapped action function, and a `pending` boolean — this is the standard way to handle form state
  • 6Server Actions replace API routes for mutations in most cases. Use Route Handlers when you need a public API consumed by external clients, webhooks, or non-form interactions like file streaming

Master next.js server actions

Take the Next.js Full-Stack course with hands-on lessons and challenges.

Examples

Form submission with Zod validation and error handling

typescript

This is the pattern you'll use for most forms. The action receives prevState (for useActionState) and FormData. Zod validates the input on the server, and the structured error object flows back to the client for display. revalidatePath ensures the project list shows the new entry.

Client component with useActionState for validation errors and pending state

tsx

useActionState wires the action to the form and provides three values: the last return value from the action (state), a wrapped action to pass to the form, and a pending boolean. The component re-renders with validation errors when the action returns them, and the button disables during submission.

Module-level actions file — shared across components

typescript

Putting 'use server' at the top of the file marks every exported function as a Server Action. This is the pattern for actions shared across multiple components. Each action checks authentication and ownership before mutating — never trust that the caller is who you expect.

Optimistic update with useOptimistic

tsx

useOptimistic updates the UI immediately while the server action runs in the background. If the action fails, React automatically rolls back to the original state. This gives instant feedback for toggle-style interactions where waiting for the server round trip feels sluggish.

Passing additional arguments with bind

tsx

When you need to pass data that isn't in the form (like an entity ID), use Function.prototype.bind. The bound argument arrives as the first parameter, before FormData. This is cleaner than hidden inputs because the value doesn't leak into the HTML.

Revalidating cache after mutation — path vs tag

typescript

revalidatePath invalidates a specific URL. revalidateTag invalidates every cached fetch that was tagged with that string, regardless of which page it appears on. Use both when a mutation affects multiple views. Note that redirect must come last because it throws a special exception that stops execution.

Common Mistakes

Mistake:

Defining a Server Action inside a Client Component — you'll get a build error because `'use server'` cannot be used inline in `'use client'` files

Fix:

Create a separate file (e.g., `actions.ts`) with `'use server'` at the top, then import the action into your Client Component. Only Server Components can define inline Server Actions.

Mistake:

Trusting that form data is safe because it came from your own UI — a Server Action is a public HTTP endpoint and anyone can call it with arbitrary data

Fix:

Always validate input with a library like Zod, and always check authentication and authorization inside the action. Treat every Server Action like a public API endpoint.

Mistake:

Using `onSubmit` instead of `action` on the form — this bypasses progressive enhancement and requires manual fetch handling

Fix:

Pass the Server Action to `<form action={myAction}>`. React extends the native action attribute to invoke the server function. The form will work even before JavaScript hydrates.

Mistake:

Calling `redirect()` before `revalidatePath()` — redirect throws a special exception internally, so any code after it never executes

Fix:

Always call `revalidatePath()` or `revalidateTag()` before `redirect()`. The redirect must be the last statement in your action because nothing after it will run.

Best Practices

  • Co-locate related actions in a dedicated file (e.g., `app/actions/project-actions.ts`) with `'use server'` at the top — this keeps mutations organized and makes them importable from any component
  • Always validate inputs with Zod or a similar library inside the action. Client-side validation is for UX; server-side validation is for security. Never skip server validation.
  • Use `useActionState` as your default pattern for forms — it gives you validation errors, pending state, and progressive enhancement in one hook
  • Check authentication and authorization at the start of every action. A Server Action is a public endpoint. Verify the user has permission to perform the operation, not just that they're logged in.
  • Prefer `revalidateTag` over `revalidatePath` when a mutation affects data shown on multiple pages — tag-based invalidation is more precise and doesn't require knowing every URL that displays the data
  • Return structured error objects from actions instead of throwing errors. Thrown errors surface as generic error boundaries. Returning `{ errors: ... }` lets the form display field-level validation messages.

Summary

Server Actions are async server-side functions invoked directly from React components via `'use server'`. They replace the boilerplate of creating API routes for mutations. Pass them to `<form action>` for progressive enhancement, validate inputs with Zod, check auth inside every action, and call `revalidatePath`/`revalidateTag` to keep cached data fresh. Use `useActionState` for form validation errors and pending states, and `useOptimistic` when you need instant UI feedback. Reserve Route Handlers for public APIs, webhooks, and streaming responses.

Practice Next.js with hands-on challenges

Learn next.js server actions hands-on in your IDE

Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.

Related Concepts

Related Cheatsheets

Master Next.js with Stanza

Interactive lessons and challenges, right in your code editor.

Check the free courses. No credit card.