nextjs-server-actions

CoderMariusz's avatarfrom CoderMariusz

When handling form submissions, data mutations, or any action that modifies server-side data. **Version Context**: Next.js 16.0+ uses `useActionState` (replaces deprecated `useFormState`).

⭐1stars🔀0forks📁View on GitHub🕐Updated Jan 4, 2026

When & Why to Use This Skill

This Claude skill provides expert guidance and standardized patterns for implementing Next.js Server Actions and server-side data mutations. It specifically addresses the transition to Next.js 16+, helping developers implement the `useActionState` hook correctly while ensuring robust validation, error handling, and cache revalidation for modern web applications.

Use Cases

  • Implementing secure and type-safe form submissions using Server Actions with Zod validation.
  • Managing complex UI states including pending transitions and server-side errors in Next.js 16+ using useActionState.
  • Optimizing application performance by correctly triggering revalidatePath or revalidateTag after data mutations.
  • Refactoring legacy Next.js applications to replace deprecated useFormState with modern React hooks.
  • Building interactive components that require server-side logic without manual API route management.
namenextjs-server-actions
descriptionWhen handling form submissions, data mutations, or any action that modifies server-side data. **Version Context**: Next.js 16.0+ uses `useActionState` (replaces deprecated `useFormState`).
version1.1.0
tokens~550
confidencehigh
- https//react.dev/reference/rsc/server-actions
last_validated2025-12-10
next_review2025-12-24
tags[nextjs, react, forms, server-actions, frontend]

When to Use

When handling form submissions, data mutations, or any action that modifies server-side data.

Version Context: Next.js 16.0+ uses useActionState (replaces deprecated useFormState).

Patterns

Basic Server Action

// app/actions.ts
'use server'

import { revalidatePath } from 'next/cache';

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string;

  await db.insert({ title });

  revalidatePath('/posts'); // Refresh the page data
}

Form with Server Action

// app/page.tsx
import { createPost } from './actions';

export default function Page() {
  return (
    <form action={createPost}>
      <input name="title" required />
      <button type="submit">Create</button>
    </form>
  );
}

With Validation (Zod)

'use server'

import { z } from 'zod';
import { redirect } from 'next/navigation';

const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

export async function register(formData: FormData) {
  const result = schema.safeParse({
    email: formData.get('email'),
    password: formData.get('password'),
  });

  if (!result.success) {
    return { error: result.error.flatten() };
  }

  // Process valid data
  await createUser(result.data);
  redirect('/dashboard');
}

With useActionState (Next.js 16+)

'use client'

import { useActionState } from 'react';
import { register } from './actions';

export function RegisterForm() {
  const [state, action, pending] = useActionState(register, null);

  return (
    <form action={action}>
      <input name="email" />
      {state?.error?.email && <p>{state.error.email}</p>}
      <button disabled={pending}>
        {pending ? 'Loading...' : 'Submit'}
      </button>
    </form>
  );
}

Legacy: useFormState (Next.js 15 and earlier)

'use client'

import { useFormState, useFormStatus } from 'react-dom';
import { register } from './actions';

function SubmitButton() {
  const { pending } = useFormStatus();
  return <button disabled={pending}>{pending ? 'Loading...' : 'Submit'}</button>;
}

export function RegisterForm() {
  const [state, formAction] = useFormState(register, null);

  return (
    <form action={formAction}>
      <input name="email" />
      {state?.error?.email && <p>{state.error.email}</p>}
      <SubmitButton />
    </form>
  );
}

Event Handler with useTransition

'use client'

import { useTransition } from 'react';
import { updateProfile } from './actions';

export function ProfileButton() {
  const [isPending, startTransition] = useTransition();

  return (
    <button onClick={() => startTransition(async () => {
      await updateProfile();
    })}>
      {isPending ? 'Saving...' : 'Update Profile'}
    </button>
  );
}

Anti-Patterns

  • Not validating input server-side
  • Forgetting revalidatePath after mutation
  • Missing error handling
  • Using deprecated useFormState in Next.js 16+
  • Not handling pending states

Verification Checklist

  • Input validated with Zod
  • revalidatePath/revalidateTag after mutations
  • Error handling with return values
  • Using useActionState for Next.js 16+
  • Pending states properly displayed