Server Actions: Forms Without API Routes
Server Actions are async functions that run on the server, triggered from Client Components. They eliminate the need for separate API route handlers for data mutations — form submissions, database writes, and file uploads all work without a custom endpoint.
Defining a Server Action
// app/actions/posts.ts
'use server'; // Everything in this file runs on the server
import { db } from '@/lib/db';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
const slug = title.toLowerCase().replace(/\s+/g, '-');
if (!title || !content) {
throw new Error('Title and content are required');
}
await db.post.create({
data: { title, content, slug },
});
revalidatePath('/blog'); // Invalidate the blog list cache
redirect('/blog'); // Redirect after creation
}
Using a Server Action in a Form
// app/blog/new/page.tsx (Server Component — no 'use client' needed)
import { createPost } from '@/actions/posts';
export default function NewPostPage() {
return (
<form action={createPost}>
<input
name="title"
placeholder="Post title"
className="w-full border p-2 rounded"
required
/>
<textarea
name="content"
placeholder="Write your post..."
className="w-full border p-2 rounded mt-4"
rows={10}
required
/>
<button
type="submit"
className="mt-4 px-6 py-2 bg-blue-600 text-white rounded"
>
Publish
</button>
</form>
);
}
Inline Server Actions
For simple cases, define the action inline in the Server Component:
// app/todos/page.tsx
export default async function TodosPage() {
const todos = await getTodos();
async function addTodo(formData: FormData) {
'use server'; // Inline directive
const text = formData.get('text') as string;
await db.todo.create({ data: { text } });
revalidatePath('/todos');
}
return (
<div>
<form action={addTodo}>
<input name="text" placeholder="New todo" required />
<button type="submit">Add</button>
</form>
<ul>
{todos.map(todo => <li key={todo.id}>{todo.text}</li>)}
</ul>
</div>
);
}
Server Actions work without JavaScript enabled in the browser — they degrade gracefully to standard HTML form submissions.