Server Actions
Overview
If Server Components are the modern way to read data from the backend, Server Actions are the modern way to write data to the backend (e.g., submitting forms, updating databases).
Historically, to handle a form submission in React, you had to:
1. Create a useState controlled form.
2. Write a separate Express.js or Next.js API Route (e.g., /api/submit).
3. Use fetch() in the component to send a JSON POST request to that URL.
Server Actions eliminate this entire process. A Server Action is literally an asynchronous JavaScript function defined with the `"use server"` directive. You can pass this function directly to the action attribute of an HTML <form>. When the user clicks submit, React automatically encrypts the form data, bridges the network gap, and executes your backend function directly on the server.
Syntax
// 1. Define the Server Action
// This function runs EXCLUSIVELY on the secure backend server.
async function updateUser(formData) {
"use server"; // This directive marks it as an exposed endpoint
const username = formData.get('username');
// Direct database mutation! No API routes required.
await db.user.update({ name: username });
}
// 2. The UI Component (Can be Server OR Client component)
export default function ProfileForm() {
return (
// 3. We pass the function reference directly to the HTML action attribute!
<form action={updateUser}>
<input type="text" name="username" placeholder="New name" />
<button type="submit">Save</button>
</form>
);
}Common Pitfalls
- Security and Validation: Because a Server Action is an exposed backend endpoint (React generates a hidden URL for it under the hood), malicious users can theoretically hit it directly. You MUST validate user authorization and sanitize the input data (using Zod) inside the Server Action before touching your database.
Interview Questions
Progressive Enhancement means an application should still fundamentally work even if JavaScript fails to load or is disabled in the browser. Because Server Actions are attached directly to the native <form action={...}> attribute, the browser can natively submit the form via standard HTTP POST request without requiring any client-side JavaScript.
<form> element (e.g., clicking a 'Like' button)?While Server Actions are designed for forms, you can invoke them anywhere by wrapping them in the startTransition API or directly importing and calling them in an onClick handler of a Client Component.
Real-World Example
Mutating Data and Revalidating the Cache: The combination of Server Actions and revalidatePath allows you to build completely dynamic, real-time database applications without writing a single useState, useEffect, or fetch API call.
import { revalidatePath } from 'next/cache';
// action.js
export async function addComment(formData) {
"use server";
const text = formData.get('comment');
await db.comments.create({ text });
// This is the magic of Next.js Server Actions.
// We tell the server: "The database changed. Purge the cache for this URL,
// re-run the Server Components, and instantly push the fresh HTML to the browser!"
revalidatePath('/blog/post-1');
}
// page.js
export default function CommentSection() {
return (
<form action={addComment}>
<textarea name="comment"></textarea>
<button type="submit">Post Comment</button>
</form>
);
}Check Your Knowledge
Test your understanding of Server Actions with these quick questions.