Mutations and Revalidation
The idea in one sentence
Section titled “The idea in one sentence”A mutation is a four-step flow: validate the input, write to the database, call revalidateTag or revalidatePath to refresh the affected cached data, and optionally redirect — so the UI reflects the change without a manual reload.
The full mutation flow
Section titled “The full mutation flow”Inside the action you validate first, then mutate, then revalidate the data that just changed. Only after revalidation do you redirect, because redirect throws internally and stops the rest of the function from running.
// app/actions.ts — validate, write, revalidate, redirect'use server'
import { revalidatePath } from 'next/cache'import { redirect } from 'next/navigation'import { z } from 'zod'
const schema = z.object({ title: z.string().min(1) })
export async function createPost(formData: FormData) { const parsed = schema.safeParse({ title: formData.get('title') }) if (!parsed.success) { return { error: 'Invalid title' } }
await db.post.create({ data: { title: parsed.data.title } })
revalidatePath('/posts') redirect('/posts')}revalidateTag vs revalidatePath
Section titled “revalidateTag vs revalidatePath”revalidatePath('/posts') invalidates everything cached for that route. revalidateTag('posts') invalidates only the fetches you tagged with 'posts', so it is more surgical when the same data appears on several routes. Tag your reads, then invalidate by tag after the write.
// app/posts/data.ts — tag the read so you can revalidate by tag laterexport async function getPosts() { const res = await fetch('https://api.example.com/posts', { next: { tags: ['posts'] }, }) return res.json()}// app/actions.ts — invalidate only the tagged data'use server'
import { revalidateTag } from 'next/cache'
export async function publishPost(id: string) { await db.post.update({ where: { id }, data: { published: true } }) revalidateTag('posts')}How the client caches get refreshed
Section titled “How the client caches get refreshed”This ties back to the caching module. On the server, revalidation clears the Data Cache entry for the affected fetch. On the client, the Server Action response also refreshes the Router Cache for the current route, so the already-rendered segments are replaced with fresh server output. The user sees the new data without a hard reload — the two caches stay consistent.
graph TD A["Action validates input"] --> B["Writes to the database"] B --> C["revalidateTag or revalidatePath"] C --> D["Data Cache entry cleared"] C --> E["Router Cache refreshed"] D --> F["UI shows fresh data"] E --> F B --> G["Optional redirect after revalidate"]