Actions and forms
Mutate and revalidate
Write data once, invalidate the affected view, and redirect only after the mutation has completed successfully.
A successful mutation can leave cached or previously rendered data stale. The action should identify which view or cache entry is now invalid.
Perform authorization, validation, and the write first. Then call the revalidation API that matches your caching model, such as revalidatePath('/notes'), and optionally redirect to the new resource. Do not redirect from inside a catch block that accidentally treats the framework redirect as an error.
Invalidate only after the write commits. Doing it before a failed write can regenerate the old data and leave that stale result cached again. When several writes form one operation, use a transaction so the cache never advertises a half-finished state.
redirect() throws a framework control-flow signal and ends the action. Put it after the error-handling region. Also design for retries. A double click, network retry, or resubmitted browser request can invoke the action more than once. A unique constraint or idempotency key protects the data. A pending button alone does not.
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
export async function createNote(formData: FormData) {
const note = await saveValidatedNote(formData)
revalidatePath('/notes')
redirect(`/notes/${note.slug}`)
}
Create a note through the form. You should land on /notes/your-slug and see the new item on /notes without a manual refresh. Simulate a failed write: the user should stay on the form with an error, not on a detail page claiming success.
Submit twice quickly. The server should not create duplicate records if your persistence layer enforces uniqueness.
If you call revalidatePath before the database write finishes, the next visitor may briefly see stale data cached as fresh. Watch server logs and list order after a slow write to catch that ordering bug.
Lesson completed