TrueSpec

Next.js Server Actions Guide

Form mutations, revalidation, optimistic updates, and error handling

What You’ll Build

After following this guide, you will have a working implementation of next.js server actions guide in your project. Server Actions let you run server-side code directly from React components without creating API routes. Perfect for form submissions, data mutations, and revalidation. Works with progressive enhancement — forms function even without JavaScript.

Use Cases & Problems Solved

  • Implement interactive UI features that users expect from modern apps
  • Follow established patterns that scale and remain maintainable
  • Reduce boilerplate and avoid common frontend pitfalls

Prerequisites

  • Next.js 14+ with App Router

Step-by-Step Implementation

Create a Server Action

The following snippet shows how to create a server action. Copy this into your project and adjust the values for your environment.

// app/actions.ts
'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, 'Title is required'),
  content: z.string().min(10),
});

export async function createPost(formData: FormData) {
  const parsed = schema.safeParse({
    title: formData.get('title'),
    content: formData.get('content'),
  });

  if (!parsed.success) {
    return { errors: parsed.error.flatten().fieldErrors };
  }

  await db.post.create({ data: parsed.data });
  revalidatePath('/posts');
  redirect('/posts');
}

Use in a form component

The following snippet shows how to use in a form component. Copy this into your project and adjust the values for your environment.

// app/posts/new/page.tsx
import { createPost } from '../actions';
import { useActionState } from 'react';

export default function NewPostPage() {
  const [state, action, isPending] = useActionState(createPost, null);

  return (
    <form action={action}>
      <input name="title" placeholder="Post title" />
      {state?.errors?.title && <p className="error">{state.errors.title}</p>}

      <textarea name="content" placeholder="Write your post..." />
      {state?.errors?.content && <p className="error">{state.errors.content}</p>}

      <button disabled={isPending}>
        {isPending ? 'Creating...' : 'Create Post'}
      </button>
    </form>
  );
}

⚠️ Don’t Do This

❌ Forgetting ‘use server’ directive — exposes server code to client

// Missing 'use server'! This code gets bundled to the browser
// and YOUR database credentials are leaked!
export async function deleteUser(id: string) {
  await db.user.delete({ where: { id } }); // Runs in browser!!!
}

✅ Always add ‘use server’ at the top of server action files

'use server';

// This code ONLY runs on the server, never sent to browser
export async function deleteUser(id: string) {
  await db.user.delete({ where: { id } });
}

Testing

Verify your implementation with these tests:

// __tests__/next-js-server-actions-guide.test.ts
import { describe, it, expect } from 'vitest';

describe('Next.js Server Actions Guide', () => {
  it('should initialize without errors', () => {
    // Test that the setup completes successfully
    expect(() => setup()).not.toThrow();
  });

  it('should handle the primary use case', async () => {
    const result = await execute();
    expect(result).toBeDefined();
    expect(result.success).toBe(true);
  });

  it('should handle edge cases', async () => {
    // Test with empty/null input
    const result = await execute(null);
    expect(result.error).toBeDefined();
  });
});

Verification

npm run dev
# Disable JavaScript in browser — form should still work!
# Submit form — should see redirect to /posts
# Check database for new post entry

Related Specs

Beginner

React Form Validation (React Hook Form + Zod)

Type-safe forms, custom validators, error display, and submission handling

Frontend Patterns
Beginner

Toast Notification System

Custom toast component, queue management, animations, and accessibility

Frontend Patterns
Beginner

Zustand State Management

Store setup, slices, persist middleware, devtools, and TypeScript

Frontend Patterns