Next.js Directives
Use the use client, use server, and use cache directives to control where code runs.
TL;DR
- Add
'use client'to files that need state, effects, or events. - Add
'use server'to functions the browser may call on the server. - Add
'use cache'to async functions whose results can be reused.
use client
'use client'Mark a file so its components run in the browser and can use hooks.
'use client';
import { useState } from 'react';
export function Toggle() {
const [on, setOn] = useState(false);
return (
<button onClick={() => setOn(!on)}>
{on ? 'On' : 'Off'}
</button>
);
}Top of FilePlace the directive above every import, or Next.js ignores it.
'use client'; // first line
import { useEffect } from 'react';One BoundaryAdd it once at the entry point; components it imports are already client code.
// Form.tsx
'use client';
import { Input } from './Input'; // no directive neededuse server
File-LevelMark every export in a file as a Server Function the client can call.
// app/actions.ts
'use server';
export async function createPost(form: FormData) {
const title = form.get('title');
await savePost(String(title));
}InlineDefine a Server Function inside a Server Component with an inline directive.
export default function Page() {
async function subscribe(form: FormData) {
'use server';
await addEmail(String(form.get('email')));
}
return <form action={subscribe}>...</form>;
}Form ActionPass a Server Function to a form's action prop to run it on submit.
import { createPost } from './actions';
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" />
<button type="submit">Save</button>
</form>
);
}use cache
cacheComponentsTurn on Cache Components in your config before using the directive.
// next.config.ts
const nextConfig = {
cacheComponents: true,
};
export default nextConfig;'use cache'Cache the result of an async function so repeat calls skip the work.
export async function getProducts() {
'use cache';
return sql`SELECT * FROM products`;
}cacheLife()Set how long the cached result stays fresh with a preset profile.
import { cacheLife } from 'next/cache';
export async function getProducts() {
'use cache';
cacheLife('hours');
return sql`SELECT * FROM products`;
}Using Directives Safely
Validate InputTreat every Server Function as a public endpoint and check its arguments.
'use server';
export async function rename(form: FormData) {
const name = String(form.get('name') ?? '');
if (name.length < 2) {
return { error: 'Name is too short' };
}
await saveName(name);
}Check AuthVerify the user inside the function, not only on the page that calls it.
'use server';
export async function deletePost(id: string) {
const user = await getUser();
if (!user) throw new Error('Not signed in');
await removePost(id, user.id);
}Pass Request DataRead cookies outside a cached function, then pass the value in as an argument.
const theme = (await cookies()).get('theme')?.value;
const data = await getHome(theme ?? 'light');
async function getHome(theme: string) {
'use cache';
return loadHome(theme);
}Tips
- Keep Server Functions in a separate
actions.tsfile marked'use server', so both Server and Client Components can import them. - Call
cacheLife()inside every'use cache'function, so the lifetime of the cached data is clear to the next reader.
Warnings
- A
'use server'function is a public endpoint, so always validate its input and check the user's permissions inside it. - The
'use cache'directive only works after you setcacheComponents: trueinnext.config.ts, so enable that option first.
In Practice
Save a new post with a Server Function, then refresh the list so the user sees it.
- The actions file is marked use server, so its export can be a form action.
- The function validates the title before it touches the database.
- revalidatePath clears the cached list so the new post appears.
- The page passes the function to the form with no client JavaScript needed.
// app/posts/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
import { sql } from '@/lib/db';
export async function createPost(form: FormData) {
const title = String(form.get('title') ?? '').trim();
if (!title) return;
await sql`INSERT INTO posts (title) VALUES (${title})`;
revalidatePath('/posts');
}
// app/posts/page.tsx
import { createPost } from './actions';
export default function Page() {
return (
<form action={createPost}>
<input name="title" required />
<button type="submit">Add post</button>
</form>
);
}FAQ
'use client' marks components that run in the browser. 'use server' marks functions that run on the server but can be called from the browser. They are not opposites. Server Components need no directive at all.
No. Components in the app folder are Server Components by default. 'use server' is only for Server Functions, such as form actions.
It caches the return value of an async function or component. Calls with the same arguments reuse the stored result. It requires cacheComponents: true in your config.
A directive must be the first statement, above all imports. 'use server' and 'use cache' can also be the first line inside a single function.