Next.js Server Actions are async functions that execute on the server when triggered by a form submission or client event. They let you save database records and refresh cached pages without writing separate REST API endpoints.
Mark a function or an entire module with the 'use server' directive at the top. Next.js creates a secure POST endpoint behind the scenes and wires your form to it automatically.
flowchart TD
A["User Submits Form"] --> B["Server Action ('use server')"]
B --> C["Validate Auth & Input"]
C --> D["Write to Database"]
D --> E["revalidatePath('/tasks')"]
E --> F["Updated Page Renders"]
Keep Server Actions in a dedicated file such as app/actions/taskActions.ts. Always verify user authentication and validate incoming FormData before touching the database.
// app/actions/taskActions.ts
'use server';
import { revalidatePath } from 'next/cache';
export async function createTask(formData: FormData) {
const title = String(formData.get('title') ?? '').trim();
if (title.length < 3) {
throw new Error('Task title must be at least 3 characters.');
}
await fetch('https://api.example.com/tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title }),
});
revalidatePath('/tasks');
}
Pass the Server Action directly to the <form action={createTask}> prop. Inside a Client Component button, use useFormStatus from react-dom to disable double clicks while saving.
// app/tasks/SubmitButton.tsx
'use client';
import { useFormStatus } from 'react-dom';
export default function SubmitButton() {
const { pending } = useFormStatus();
return (
<button
type="submit"
disabled={pending}
className="px-4 py-2 bg-blue-600 text-white rounded disabled:bg-gray-400"
>
{pending ? 'Saving...' : 'Add Task'}
</button>
);
}
// app/tasks/NewTaskForm.tsx
import { createTask } from '@/app/actions/taskActions';
import SubmitButton from './SubmitButton';
export default function NewTaskForm() {
return (
<form action={createTask} className="space-y-4 p-4 border rounded">
<label className="block text-sm font-medium">
Task Title
<input
type="text"
name="title"
required
className="mt-1 block w-full border rounded p-2"
/>
</label>
<SubmitButton />
</form>
);
}
useFormStatus in the same component that renders <form>: useFormStatus only reads the status of a parent <form>, so put the button in its own child component.revalidatePath after mutations: Without revalidation, the page may continue showing stale cached rows after saving.Yes, when passed to <form action={myAction}>, HTML forms still submit natively via POST to the server without client JavaScript.
Yes, pair your Server Action with React's useActionState hook to return field error messages without throwing uncaught exceptions.
GET data reads?No, Server Actions are designed for mutations (POST-like writes); fetch read data directly in Server Components instead.
Sign in to track your learning journey, earn industry-recognized certificates, and join our elite developer community.
Quick Access With