The use server Directive — Defining and Organizing Server Actions
In this tutorial, you will learn about The use server Directive. We cover key concepts, practical examples, and best practices to help you master this topic.
The 'use server' directive marks a function as a Server Action that executes on the server and can be called from Client Components or form elements.
What You'll Learn
You will understand how 'use server' works at the file and function level, how to organize Server Actions in separate modules, and best practices for structuring action files.
Why It Matters
Properly organizing Server Actions keeps your codebase maintainable, prevents accidental exposure of server logic, and makes it easy to reuse actions across components.
Real-World Use
DodaTech organizes Server Actions in a lib/actions/ directory with one file per domain (users, posts, comments), keeping the action logic separate from component concerns.
flowchart LR
subgraph Client[Client Component]
A[Form Submit]
B[Button Click]
end
subgraph Server[Server Actions]
C[createUser action]
D[updatePost action]
E[deleteComment action]
F[validate and mutate]
end
subgraph File[Server Action File]
G['use server']
H[export async functions]
end
A --> C
B --> D
A --> E
G --> H
H --> F
style Server fill:#1e293b,color:#fff
style File fill:#0f172a,color:#fff
File-Level use server
When 'use server' is at the top of a file, all exported functions in that file become Server Actions.
// lib/actions/users.js
'use server';
import { db } from '@/lib/database';
import { revalidatePath } from 'next/cache';
export async function createUser(formData) {
const name = formData.get('name');
const email = formData.get('email');
await db.users.create({ name, email });
revalidatePath('/users');
return { success: true };
}
export async function deleteUser(userId) {
await db.users.delete(userId);
revalidatePath('/users');
return { success: true };
}
export async function updateUserRole(userId, role) {
await db.users.update(userId, { role });
revalidatePath('/users');
return { success: true };
}
Expected output: All three exported functions become Server Actions. They can be imported and used in any Server or Client Component across the application.
Function-Level use server
When 'use server' is inside a function body, only that specific function becomes a Server Action.
// app/posts/[id]/page.js — Server Component
import { db } from '@/lib/database';
export default async function PostPage({ params }) {
const post = await db.posts.findById(params.id);
async function addComment(formData) {
'use server';
const text = formData.get('text');
await db.comments.create({ postId: params.id, text });
revalidatePath(`/posts/${params.id}`);
return { success: true };
}
return (
<div>
<h1>{post.title}</h1>
<form action={addComment}>
<textarea name="text" required />
<button type="submit">Add Comment</button>
</form>
</div>
);
}
Expected output: The addComment function is a Server Action scoped to this component file. It has access to params.id directly without passing it through form data.
Importing Server Actions in Client Components
Server Actions from separate files can be imported directly into Client Components.
'use client';
// app/users/CreateUserForm.jsx
import { createUser } from '@/lib/actions/users';
import { useActionState } from 'react';
export default function CreateUserForm() {
const [state, formAction, pending] = useActionState(createUser, null);
return (
<form action={formAction}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<button type="submit" disabled={pending}>
{pending ? 'Creating...' : 'Create User'}
</button>
{state?.error && <p style={{ color: 'red' }}>{state.error}</p>}
{state?.success && <p style={{ color: 'green' }}>User created!</p>}
</form>
);
}
Expected output: The Client Component imports the createUser Server Action and uses it in a form. The action runs on the server but is invoked from the client.
Type Safety with Server Actions
Server Actions work with TypeScript for type-safe form data handling.
// lib/actions/users.ts
'use server';
import { z } from 'zod';
const createUserSchema = z.object({
name: z.string().min(2).max(100),
email: z.string().email(),
role: z.enum(['user', 'admin']).default('user'),
});
export async function createUser(formData: FormData) {
const raw = {
name: formData.get('name'),
email: formData.get('email'),
role: formData.get('role') || 'user',
};
const parsed = createUserSchema.safeParse(raw);
if (!parsed.success) {
return { errors: parsed.error.flatten().fieldErrors };
}
const user = await db.users.create(parsed.data);
revalidatePath('/users');
return { success: true, user };
}
Expected output: Type-safe Server Action with Zod validation. Parsed data is typed and safe to use. Validation errors return structured field messages.
Common Mistakes
Putting use server in files that also export client utilities: A file with
'use server'at the top should only export Server Actions. Move utility functions to separate files.Using function-level use server unnecessarily in a use server file: If the file already has
'use server'at the top, individual functions do not need the directive.Importing Server Actions from files without use server: Without the directive, the function runs on the client. Always verify the file starts with
'use server'.Not handling errors in Server Actions: Wrap database operations in try/catch. Return structured error objects instead of throwing.
Exposing internal functions as Server Actions by accident: Only export functions that should be callable from the client. Keep internal helpers as non-exported functions.
Practice Questions
- What is the difference between file-level and function-level use server?
File-level applies to all exported functions in the file. Function-level applies only to the specific function. Both make functions callable from the client.
- Can a file with use server export non-action functions?
Yes. Non-exported functions in a use server file are internal helpers. Only exported functions become Server Actions.
- How do you import a Server Action in a Client Component?
Import the function directly. The framework handles the server-client boundary automatically.
- What libraries can you use for Server Action validation?
Zod, Yup, or any validation library. Server Actions run on the server and have access to all server-side Node.js modules.
- Can Server Actions return React components?
No. Server Actions return serializable data (objects, arrays, strings, numbers). They cannot return JSX.
Challenge
Organize a set of Server Actions for a blog application into separate files: posts.js (create, update, delete), comments.js (add, moderate, delete), and users.js (manage profile, update settings). Each file should have proper validation.
Frequently Asked Questions
Mini Project
Create a lib/actions/products.js file with Server Actions for createProduct (with Zod validation), updateProduct (partial update), deleteProduct, and archiveProduct. Import and use them in a Client Component admin panel.
What's Next
Learn about Client Components to understand how interactive components work alongside Server Components.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro