Skip to content

Server Actions — Server-Side Mutations from Client Components

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Server Actions. We cover key concepts, practical examples, and best practices to help you master this topic.

Server Actions are functions marked with 'use server' that run on the server when called from form submissions or event handlers, eliminating the need for separate API routes.

What You'll Learn

You will understand how Server Actions work, how to define them, how to use them with forms and event handlers, and how they integrate with Next.js revalidation.

Why It Matters

Server Actions reduce boilerplate by combining API route logic and form handling into a single function. They work with plain HTML forms, providing progressive enhancement without JavaScript.

Real-World Use

DodaZIP's file sharing portal uses Server Actions for file upload, folder creation, and permission updates, handling all mutations without a single custom API route.

flowchart LR
    A[Browser Form] --> B[Server Action]
    B --> C[Validate Input]
    C --> D[Database Mutation]
    D --> E[Revalidate Cache]
    E --> F[Return Response]
    style B fill:#1e293b,color:#fff
    style D fill:#0f172a,color:#fff

Defining a Server Action

A Server Action is an async function with 'use server' at the top of its body. It can be defined inline in a Server Component or in a separate file.

async function createUser(formData) {
  'use server';
  const name = formData.get('name');
  const email = formData.get('email');
  const role = formData.get('role');

  if (!name || !email) {
    return { error: 'Name and email are required' };
  }

  await db.users.create({ name, email, role });
  revalidatePath('/users');
  return { success: true, id: result.id };
}

Expected output: When the form is submitted, the Server Action validates the data, creates a user in the database, revalidates the users page, and returns a success response with the new user ID.

Using Server Actions with Forms

Server Actions integrate directly with the HTML form element. No JavaScript event handlers needed.

export default function CreateUserPage() {
  return (
    <form action={createUser}>
      <div>
        <label htmlFor="name">Name</label>
        <input id="name" name="name" required />
      </div>
      <div>
        <label htmlFor="email">Email</label>
        <input id="email" name="email" type="email" required />
      </div>
      <div>
        <label htmlFor="role">Role</label>
        <select id="role" name="role">
          <option value="user">User</option>
          <option value="admin">Admin</option>
        </select>
      </div>
      <button type="submit">Create User</button>
    </form>
  );
}

Expected output: A form that submits to the createUser Server Action. The form works without JavaScript because the action attribute points to a server function. On submission, the page revalidates and shows the updated user list.

Server Actions from Client Components

You can import Server Actions into Client Components and call them from event handlers or useEffect.

'use client';
import { createUser } from './actions';

export function CreateUserButton({ userData }) {
  const [pending, setPending] = useState(false);
  const [result, setResult] = useState(null);

  async function handleClick() {
    setPending(true);
    const formData = new FormData();
    formData.append('name', userData.name);
    formData.append('email', userData.email);
    const res = await createUser(formData);
    setResult(res);
    setPending(false);
  }

  return (
    <div>
      <button onClick={handleClick} disabled={pending}>
        {pending ? 'Creating...' : 'Create User'}
      </button>
      {result?.error && <p style={{ color: 'red' }}>{result.error}</p>}
      {result?.success && <p style={{ color: 'green' }}>User created!</p>}
    </div>
  );
}

Expected output: A button that calls the Server Action on click, shows a loading state, and displays success or error feedback without navigating away from the page.

Server Actions with useActionState

React 19 introduced useActionState to handle form state and pending status declaratively.

'use client';
import { useActionState } from 'react';
import { updateProfile } from './actions';

const initialState = { error: null, success: false };

function ProfileForm({ user }) {
  const [state, formAction, pending] = useActionState(updateProfile, initialState);

  return (
    <form action={formAction}>
      <input type="hidden" name="userId" value={user.id} />
      <div>
        <label>Display Name</label>
        <input name="displayName" defaultValue={user.displayName} />
      </div>
      <div>
        <label>Bio</label>
        <textarea name="bio" defaultValue={user.bio} />
      </div>
      <button type="submit" disabled={pending}>
        {pending ? 'Saving...' : 'Save Profile'}
      </button>
      {state.error && <p style={{ color: 'red' }}>{state.error}</p>}
      {state.success && <p style={{ color: 'green' }}>Profile saved!</p>}
    </form>
  );
}

Expected output: A profile form that updates user data via a Server Action. The button shows a pending state while saving, and success or error feedback displays after completion.

Common Mistakes

  1. Returning non-serializable values from Server Actions: Server Action return values must be serializable (plain objects, arrays, strings, numbers). Functions, class instances, and symbols cause errors.

  2. Putting use server in the wrong position: The 'use server' directive must be the first line of the function body, not after other statements.

  3. Forgetting to revalidate after mutations: After creating, updating, or deleting data, call revalidatePath or revalidateTag to update the cached page.

  4. Not validating input in Server Actions: Client-side validation can be bypassed. Always validate and sanitize input inside the Server Action before mutating data.

  5. Using Server Actions for read operations: Server Actions are for mutations. Use async Server Components or route handlers for reading data.

Practice Questions

  1. What does the 'use server' directive do?

It marks an async function as a Server Action that runs on the server when called from a form or event handler.

  1. How do Server Actions improve progressive enhancement?

They work with plain HTML forms using the action attribute. The form submits without JavaScript and the Server Action processes it on the server.

  1. What is the purpose of revalidatePath in a Server Action?

It tells Next.js to re-fetch and re-render the specified route after the mutation, ensuring the page shows updated data.

  1. Can you call a Server Action from a Server Component?

Yes. Server Actions can be called directly from Server Components for server-initiated mutations.

  1. What happens if a Server Action throws an error?

The error propagates to the caller. In a form, the error boundary catches it. In a Client Component, you should wrap the call in try/catch.

Challenge

Build a task management form that creates tasks via a Server Action, validates input (title required, due date in the future), revalidates the task list, and returns appropriate error messages.

Frequently Asked Questions

Do Server Actions work without JavaScript?

Yes. Server Actions used with the HTML form action attribute work without JavaScript. They provide progressive enhancement by default.

Can I call Server Actions from API routes?

There is no benefit to calling Server Actions from API routes. Use the database logic directly in both places or extract it into a shared utility.

Are Server Actions secure?

Server Actions run on the server and never expose their code to the client. However, always validate input and check authentication inside the action.

What is the size limit for Server Action payloads?

There is no fixed limit, but large payloads affect performance. For file uploads, use dedicated upload endpoints instead.

Can I use Server Actions with external APIs?

Yes. Server Actions can call external APIs, databases, or any server-side service. They are regular async functions that run on the server.

Mini Project

Build a comment system with a Server Action that validates the comment, saves it to the database, revalidates the post page, and returns the new comment ID.

What's Next

Continue to Form Actions to learn advanced form handling patterns with Server Actions.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro