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
React Form Validation (React Hook Form + Zod)
Type-safe forms, custom validators, error display, and submission handling
Toast Notification System
Custom toast component, queue management, animations, and accessibility