last update
Jun 2, 2026
license
MITv1.0.0
format
SKILL.md
- Install
// Downloads SKILL.md into ~/.claude/skills/nextjs-server-actions/
$ curl -fsSL --create-dirs -o ~/.claude/skills/nextjs-server-actions/SKILL.md https://claudeskil.com/skills/nextjs-server-actions/SKILL.md - Invoke
// Inside Claude Code, type the skill name as a slash command
> /nextjs-server-actions give my landing page a hero treatment - Or copy this from the README
// extracted ts block
$ // app/todos/actions.ts 'use server' import { revalidateTag } from 'next/cache' import { z } from 'zod' const Schema = z.object({ title: z.string().min(1) })
- name:
- Next.js Server Actions
- slug:
- nextjs-server-actions
- version:
- v1.0.0
- license:
- MIT
- author:
- @chiragthummar16
- categories:
- tags:
- #nextjs#server-actions#forms#react#mutations
- description:
Server Actions done right — form mutations, optimistic UI, useOptimistic, revalidatePath/Tag.
README.md
Next.js Server Actions
A focused skill for building data mutations in the Next.js App Router the way they're meant to work: async functions marked 'use server', wired to forms or called imperatively, with progressive enhancement, optimistic UI, and cache revalidation. It's for developers who've gotten Server Actions running but keep hitting the rough edges — stale lists after a write, double-submits, leaked error details, and the useFormState/useOptimistic mental model.
When to use it
- A form submits, the row saves, but the list above it doesn't update until a refresh.
- You want a "Like" or "Add to cart" button to flip instantly, then reconcile with the server.
- You need a mutation to work even before JavaScript hydrates (a real
<form action={...}>). - You're returning
{ error: e.message }and worried about what that leaks in production. - You can't decide between
revalidatePathandrevalidateTag.
How it works
The skill covers the actual primitives. Define actions either inline in a Server Component or in a 'use server' file (every export becomes a callable endpoint — validate inputs accordingly). Bind them with <form action={createTodo}> for progressive enhancement, or call them imperatively inside startTransition. For form state and validation messages, use useActionState (the current name; useFormState from react-dom is the older alias). Disable submit buttons with useFormStatus's pending. Drive instant feedback with useOptimistic. After a write, call revalidatePath('/todos') to refresh a route or revalidateTag('todos') to invalidate everything fetched with that cache tag. Use redirect() to navigate post-mutation, and cookies()/headers() for auth inside the action.
Example
curl -fsSL --create-dirs -o ~/.claude/skills/nextjs-server-actions/SKILL.md https://claudeskil.com/skills/nextjs-server-actions/SKILL.md
// app/todos/actions.ts
'use server'
import { revalidateTag } from 'next/cache'
import { z } from 'zod'
const Schema = z.object({ title: z.string().min(1) })
export async function createTodo(_prev: unknown, formData: FormData) {
const parsed = Schema.safeParse({ title: formData.get('title') })
if (!parsed.success) return { error: 'Title is required' }
try {
await db.todo.create({ data: parsed.data })
revalidateTag('todos')
return { ok: true }
} catch {
return { error: 'Could not save. Try again.' } // never expose e.message
}
}
Tips & gotchas
- Every export from a
'use server'file is a public POST endpoint. Authorize and validate inside each one — never trust the client. useActionStatereturns[state, formAction, isPending]; passformActionto<form action>, not the raw action.revalidatePath/revalidateTagonly work if yourfetch/data layer is actually cached and tagged; uncached reads ignore them.- Don't
throwraw errors to the client — return a typed result object so stack traces stay server-side.
FAQ
revalidatePath vs revalidateTag? Path invalidates one route's cache; tag invalidates every cached fetch carrying that tag, across routes. Tags scale better for shared data.
Can actions run on GET? No — they're POST-only. Use route handlers or Server Component reads for fetching.
Why is my optimistic update flickering? You likely mutate state outside startTransition, so React reverts before the action resolves. Wrap the call.
$ cat reviews/