What is a Server Action?
A Server Action is an async function marked with 'use server' that runs only on the server but can be called directly from a Client or Server Component — no need to manually write a fetch() call to an API route.
// app/actions.ts
'use server';
import { db } from '@/lib/db';
import { revalidatePath } from 'next/cache';
export async function addStudent(formData: FormData) {
const name = formData.get('name') as string;
await db.students.create({ data: { name } });
revalidatePath('/students');
}
Using a Server Action directly in a <form>
// app/students/new/page.tsx (Server Component)
import { addStudent } from '@/app/actions';
export default function NewStudentPage() {
return (
<form action={addStudent}>
<input name="name" placeholder="Student name" required />
<button type="submit">Add Student</button>
</form>
);
}
This works even with JavaScript disabled — the <form> posts normally — and is progressively enhanced with client-side transitions when JS is available.
Combining with React 19's useActionState
For validation errors and pending state in a Client Component:
'use client';
import { useActionState } from 'react';
import { addStudent } from '@/app/actions';
const initialState = { error: null };
export default function StudentForm() {
const [state, formAction, isPending] = useActionState(addStudent, initialState);
return (
<form action={formAction}>
<input name="name" required />
{state?.error && <p className="error">{state.error}</p>}
<button disabled={isPending}>{isPending ? 'Saving...' : 'Add'}</button>
</form>
);
}
// updated action to return validation state
'use server';
export async function addStudent(prevState: unknown, formData: FormData) {
const name = formData.get('name') as string;
if (!name) return { error: 'Name is required' };
await db.students.create({ data: { name } });
revalidatePath('/students');
return { error: null };
}
Calling a Server Action from an event handler
Server Actions aren't limited to forms — call them from any event, like a delete button:
'use client';
import { deleteStudent } from '@/app/actions';
function DeleteButton({ id }: { id: number }) {
return <button onClick={() => deleteStudent(id)}>Delete</button>;
}
Security note
Server Actions create a public HTTP endpoint under the hood, so always re-validate input and re-check permissions/authentication inside the action itself — never trust that a button being hidden in the UI is enough protection.