Server Actions
Learn how to use Next.js Server Actions for mutations
Learn how to use Next.js Server Actions for secure server-side mutations.
Goal
By the end of this recipe, you'll have created and used Server Actions in your application.
Prerequisites
- A working Plainform installation
- Basic knowledge of React and Next.js
Steps
Create Server Action
Create a server action file:
'use server';
import { auth } from '@clerk/nextjs/server';
import { prisma } from '@/lib/prisma/prisma';
import { revalidatePath } from 'next/cache';
export async function createPost(formData: FormData) {
const { userId } = await auth();
if (!userId) {
throw new Error('Unauthorized');
}
const title = formData.get('title') as string;
const content = formData.get('content') as string;
const post = await prisma.post.create({
data: {
title,
content,
authorId: userId,
},
});
revalidatePath('/posts');
return { success: true, post };
}Always add 'use server' directive at the top of server action files.
Use in Client Component
Call the server action from a client component:
'use client';
import { createPost } from '@/app/actions/posts';
import { useTransition } from 'react';
export function CreatePostForm() {
const [isPending, startTransition] = useTransition();
const handleSubmit = async (formData: FormData) => {
startTransition(async () => {
const result = await createPost(formData);
if (result.success) {
alert('Post created!');
}
});
};
return (
<form action={handleSubmit}>
<input name="title" placeholder="Title" required />
<textarea name="content" placeholder="Content" required />
<button type="submit" disabled={isPending}>
{isPending ? 'Creating...' : 'Create Post'}
</button>
</form>
);
}Add Validation
Add input validation with Zod:
'use server';
import { z } from 'zod';
const createPostSchema = z.object({
title: z.string().min(1).max(100),
content: z.string().min(1).max(5000),
});
export async function createPost(formData: FormData) {
const { userId } = await auth();
if (!userId) {
throw new Error('Unauthorized');
}
const data = {
title: formData.get('title'),
content: formData.get('content'),
};
const validated = createPostSchema.parse(data);
const post = await prisma.post.create({
data: {
...validated,
authorId: userId,
},
});
revalidatePath('/posts');
return { success: true, post };
}Handle Errors
Add error handling:
'use client';
import { useState } from 'react';
export function CreatePostForm() {
const [isPending, startTransition] = useTransition();
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (formData: FormData) => {
setError(null);
startTransition(async () => {
try {
const result = await createPost(formData);
if (result.success) {
alert('Post created!');
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to create post');
}
});
};
return (
<form action={handleSubmit}>
{error && <div className="text-red-500">{error}</div>}
<input name="title" placeholder="Title" required />
<textarea name="content" placeholder="Content" required />
<button type="submit" disabled={isPending}>
{isPending ? 'Creating...' : 'Create Post'}
</button>
</form>
);
}Server Action Best Practices
Always Authenticate
Check user authentication in every server action:
const { userId } = await auth();
if (!userId) {
throw new Error('Unauthorized');
}Validate Input
Use Zod or similar for input validation:
const schema = z.object({
email: z.string().email(),
name: z.string().min(1),
});
const validated = schema.parse(data);Revalidate Cache
Revalidate affected paths after mutations:
revalidatePath('/posts');
revalidatePath('/dashboard');Return Serializable Data
Only return JSON-serializable data:
// Good
return { success: true, id: post.id };
// Bad - Date objects aren't serializable
return { success: true, post };Common Issues
"use server" Missing
- Add
'use server'directive at the top of the file - Ensure it's the first line (before imports)
Authentication Fails
- Verify Clerk middleware is configured
- Check that
auth()is imported from@clerk/nextjs/server
Data Not Updating
- Call
revalidatePath()after mutations - Verify the path matches the page route
Next Steps
- Add Rate Limiting - Protect your API routes
Related Documentation
- Server Actions - Next.js Server Actions guide
- Form Patterns - Form handling patterns
How is this guide ?
Last updated on