Skip to content

Mutations and Revalidation

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.

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')
}

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 later
export 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')
}

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"]
The mutation and revalidation flow
What is the correct order of the mutation flow?
Why must redirect come after revalidation in an action?
When is revalidateTag more useful than revalidatePath?
Which client-side cache does the action response refresh?