Skip to main content

'use client' Directive: Common Pitfalls

The 'use client' directive marks a file as a client component, indicating that its code and all its imports run in the browser. Misplacing or overusing this directive is the most common mistake in Server Components applications. A single misplaced 'use client' at the top of a layout can disable server rendering for your entire app. This article covers the seven most dangerous pitfalls and how to avoid them.

Pitfall 1: Over-Using 'use client' in Layouts​

Layouts should be server components by default. Adding 'use client' to a layout disables server rendering for the entire subtree. All child components become client components, even if they don't need to be.

// ❌ Disables server rendering for entire app
'use client';

export default function RootLayout({ children }) {
return (
<html>
<body>{children}</body>
</html>
);
}

This mistake is surprisingly common. Developers add 'use client' to a layout thinking they need it for styling or analytics, not realizing they've disabled all server-side benefits for the entire application.

// ✅ Keep layout as server component
export default function RootLayout({ children }) {
return (
<html>
<body>{children}</body>
</html>
);
}

If you need client-side functionality in the layout, extract it into a separate client component:

// ✅ Keep layout server; wrap with client provider
import { ClientAnalytics } from './client-analytics';

export default function RootLayout({ children }) {
return (
<html>
<body>
<ClientAnalytics />
{children}
</body>
</html>
);
}
// components/client-analytics.tsx
'use client';

import { useEffect } from 'react';

export function ClientAnalytics() {
useEffect(() => {
// Client-side analytics initialization
}, []);

return null;
}

Pitfall 2: Putting 'use client' Too High in the Tree​

Every 'use client' directive applies to the entire file and all its imports. Placing it too high in the component hierarchy marks too many components as client components.

// ❌ Marks all utilities as client code
'use client';

import { Button } from './button';
import { Card } from './card';
import { formatDate } from './utils';

export function Dashboard() {
return <Card title="Dashboard"><Button>Action</Button></Card>;
}

Even pure utility functions like formatDate become client code, wasting bundle space.

// ✅ Keep 'use client' at the leaf level
// components/dashboard.tsx
'use client';

import { Button } from './button';
import { Card } from './card';
import { formatDate } from './utils'; // Still runs on client due to import

export function Dashboard() {
return <Card title="Dashboard"><Button>Action</Button></Card>;
}

// lib/utils.ts — no 'use client' directive
export function formatDate(date: Date) {
return date.toLocaleDateString();
}

The best practice: mark only the leaf components that actually need browser APIs as 'use client'. Utility functions, types, and data should live in separate files without the directive.

Pitfall 3: Passing Non-Serializable Data to Client Components​

Client components receive props from server components. Those props must be serializable to JSON. Passing functions, class instances, or other non-serializable values causes runtime errors.

// ❌ Attempting to pass a function to a client component
async function ServerComponent() {
const handleClick = () => console.log('clicked');

return <ClientComponent onClick={handleClick} />;
// Error: functions are not serializable
}

// components/client-component.tsx
'use client';

export function ClientComponent({ onClick }) {
return <button onClick={onClick}>Click me</button>;
}
// ✅ Use a server action for callbacks
// app/actions.ts
'use server';

export async function handleClick() {
console.log('clicked on server');
// Access database, secrets, etc.
}
// Components
async function ServerComponent() {
return <ClientComponent />;
}

'use client';

import { handleClick } from '@/app/actions';

export function ClientComponent() {
return <button onClick={() => handleClick()}>Click me</button>;
}

Server actions are async functions marked with 'use server' that can be called from client components. They execute on the server and handle mutations or side effects safely.

Pitfall 4: Accidentally Importing Server-Only Code in a Client Component​

If a client component imports a module marked as server-only (with 'use server' or containing database code), Next.js will error.

// lib/db.ts — server-only database code
import { db } from 'some-orm';

export async function getUser(id: number) {
return db.users.findById(id);
}
// ❌ Error: client component cannot import server code
'use client';

import { getUser } from '@/lib/db'; // Error!

export function UserProfile({ userId }) {
const user = getUser(userId); // Error!
return <div>{user.name}</div>;
}

Next.js will throw an error like: "Cannot use the 'getUser' function in a client component. Use 'use server' or separate this function into a new file marked with 'use server'."

// ✅ Extract server code into a separate file
// lib/db.ts — server-only
export async function getUser(id: number) {
return db.users.findById(id);
}

// app/actions.ts — server action
'use server';

import { getUser } from '@/lib/db';

export async function fetchUser(id: number) {
return getUser(id);
}

// components/user-profile.tsx — client component
'use client';

import { fetchUser } from '@/app/actions';

export function UserProfile({ userId }) {
const user = use(fetchUser(userId)); // Use with Suspense or useQuery
return <div>{user.name}</div>;
}

Mark server-only modules with a "server-only" comment or use Next.js's server-only package:

// lib/db.ts
import 'server-only';

export async function getUser(id: number) {
// This file can only be imported in server code
}

Importing this file in a client component will throw a build error.

Pitfall 5: Using Browser APIs in Server Components (via Imports)​

A server component importing a client component that uses browser APIs will cause runtime errors on the server. Be explicit about boundaries.

// components/local-storage-cache.tsx
'use client';

export function useLocalStorage(key: string) {
// Uses window.localStorage — client-only
}
// ❌ Error if server component imports the client hook
async function ServerComponent() {
const cache = useLocalStorage('key'); // Error!
return <div>{cache}</div>;
}
// ✅ Wrap the browser API usage in a client component
'use client';

import { useLocalStorage } from '@/lib/hooks';

export function CacheDisplay() {
const cache = useLocalStorage('key');
return <div>{cache}</div>;
}

// Server component renders the client wrapper
async function ServerComponent() {
return <CacheDisplay />;
}

The error message is: "window is not defined" or "localStorage is not defined on the server". Always wrap browser API usage in client components.

Pitfall 6: Misunderstanding Boundaries with Async Components​

A server component calling an async function is straightforward. A client component cannot directly await in the component body (hooks-only), and a server component cannot be a child of a client component.

// ❌ Attempting async in a client component
'use client';

export function ClientComponent() {
const data = await fetchData(); // Syntax error!
return <div>{data}</div>;
}
// ✅ Use useEffect for async operations in client components
'use client';

import { useEffect, useState } from 'react';

export function ClientComponent() {
const [data, setData] = useState(null);

useEffect(() => {
fetchData().then(setData);
}, []);

return <div>{data}</div>;
}

Or use a library like React Query or SWR that abstracts the fetching logic.

Pitfall 7: Marking Shared Utility Components as 'use client'​

A utility component (no hooks, no state, no browser API) that is shared between server and client components should not have 'use client'.

// ❌ Unnecessary 'use client' on a shared utility
'use client';

export function Badge({ label }) {
return <span className="badge">{label}</span>;
}

This component doesn't use any client-specific features. Marking it as 'use client' wastes bundle space.

// ✅ Shared utility with no 'use client' directive
export function Badge({ label }) {
return <span className="badge">{label}</span>;
}

This component can be imported and used in both server and client components without any overhead.

A rule of thumb: only mark a file with 'use client' if it directly imports and uses a client-only feature (hooks, browser APIs, event listeners).

Key Takeaways​

  • Keep 'use client' out of layouts; place it only on leaf components that actually need it.
  • Mark server-only modules with the server-only package to prevent accidental client imports.
  • Pass serializable data to client components; use server actions for callbacks and mutations.
  • Wrap browser API usage in client components; never use window or localStorage in server code.
  • Don't mark shared utility components as 'use client' unless they directly use client features.
  • Understand the boundary: server components can render client components, but not vice versa.

Frequently Asked Questions​

If I add 'use client' to a layout, does it affect only that layout's children?​

Yes. A 'use client' directive in a layout marks the layout and all its child components as client components. All routes under that layout become client-rendered, losing server-side benefits.

Can I use 'use client' in a utility file?​

Avoid it. If a utility is pure (no state, no hooks), it doesn't need 'use client'. If it uses browser APIs, consider wrapping it in a client component that exports a hook.

What's the difference between 'use server' and 'use client'?​

'use server' marks a function or file as server-only; it runs only on the server and can be called from client components. 'use client' marks a component or module as client-only; it runs only in the browser.

Can a server action call another server action?​

Yes. Server actions can call other server actions. Since both run on the server, you can share database access and secrets freely.

How do I know if a component should be 'use client'?​

Ask: Does this component use useState, useEffect, other hooks, or browser APIs? If yes, mark it 'use client'. If no, leave it as a server component.

Further Reading​