Server Actions — Server-Side Mutations from Client Components
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
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.
Putting use server in the wrong position: The
'use server'directive must be the first line of the function body, not after other statements.Forgetting to revalidate after mutations: After creating, updating, or deleting data, call revalidatePath or revalidateTag to update the cached page.
Not validating input in Server Actions: Client-side validation can be bypassed. Always validate and sanitize input inside the Server Action before mutating data.
Using Server Actions for read operations: Server Actions are for mutations. Use async Server Components or route handlers for reading data.
Practice Questions
- 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.
- 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.
- 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.
- Can you call a Server Action from a Server Component?
Yes. Server Actions can be called directly from Server Components for server-initiated mutations.
- 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
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