Mutations 🔁
Reading data is half the story—mutations update state on the server or client. Next.js offers server actions, route handlers, and good old client fetches.
Forms + Server Actions 📝
Server actions let forms call server functions directly (App Router, React 19).
// app/actions.ts
'use server';
import { revalidateTag } from 'next/cache';
export async function createTodo(formData: FormData) {
const title = formData.get('title');
await db.todo.create({ data: { title } });
revalidateTag('todos');
}// app/todos/page.tsx
import { createTodo } from '../actions';
export default function TodosPage() {
return (
<form action={createTodo}>
<input name="title" />
<button type="submit">Add</button>
</form>
);
}- Server code stays on the server—no API route needed.
- Use
revalidateTagorrevalidatePathto refresh caches after mutations. - For optimistic UI, combine with client state updates.
Route Handlers (POST/PUT/DELETE) 🍳
Create API-like endpoints in app/api/.../route.ts.
// app/api/todos/route.ts
import { NextResponse } from 'next/server';
export async function POST(req: Request) {
const body = await req.json();
const todo = await db.todo.create({ data: body });
return NextResponse.json(todo, { status: 201 });
}- Supports
GET,POST,PUT,PATCH,DELETEetc. - Deployable to Node or Edge runtimes.
- Pair with
fetch('/api/todos', { method: 'POST', body: JSON.stringify(data) })from client or server.
Client-side Mutations ⚡
Sometimes you need immediate feedback or offline support.
Fetch + useState
'use client';
async function handleSubmit() {
await fetch('/api/todos', {
method: 'POST',
body: JSON.stringify({ title }),
});
router.refresh();
}- Call route handlers or external APIs directly.
- Use
router.refresh()(App Router) to re-render server components with latest data.
React Query / TanStack Query 📦
- Manage cache, retries, and optimistic updates.
const mutation = useMutation({
mutationFn: (title: string) => fetch('/api/todos', { method: 'POST', body: JSON.stringify({ title }) }),
onSuccess: () => queryClient.invalidateQueries(['todos']),
});SWR 🪣
- Lightweight hook for stale-while-revalidate.
const { mutate } = useSWR('/api/todos');
mutate(async current => [...current, newTodo], { optimisticData: true });Choosing the Right Tool 🧭
| Scenario | Best Fit |
|---|---|
| Simple form submit + cache refresh | Server actions |
| Need REST-like API for external clients | Route handlers |
| Client-heavy app with optimistic UI | React Query / SWR |
| Legacy Pages Router | API routes (pages/api) |
Analogy: Mutations are kitchen orders—server actions are direct requests to the chef, route handlers are the waiter relay, and client-side libraries are home chefs cooking immediately with plans to sync later.
Mix approaches as needed; the goal is predictable writes with tight feedback loops. ✅
Last updated on