Skip to Content
Nextjs3.2 Mutations

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 revalidateTag or revalidatePath to 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, DELETE etc.
  • 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 🧭

ScenarioBest Fit
Simple form submit + cache refreshServer actions
Need REST-like API for external clientsRoute handlers
Client-heavy app with optimistic UIReact Query / SWR
Legacy Pages RouterAPI 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