Next.js use client Directive
Problem Statement
When migrating to Next.js' app
directory, developers often encounter the error:
You're importing a component that needs useState. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.
This error occurs when using React client-side features like useState
, useEffect
, or events in components that Next.js treats as Server Components by default. Here's a minimal example triggering the error:
import { useState } from "react";
export default function Card() {
const [state, setState] = useState(""); // This causes the error
return <></>;
}
Understanding Server vs. Client Components
Key Differences
- Server Components:
- Render exclusively on the server
- Cannot use browser APIs, events, or state hooks
- Ideal for data fetching and static content
- Client Components:
- Render on both server and client
- Support interactivity, state, and effects
- Require the
'use client'
directive
Default Behavior
In Next.js app router, all components are Server Components by default. This improves performance by:
- Reducing client-side JavaScript bundles
- Enabling direct backend resource access
- Automatic request deduplication
When to Use Each
Server Components | Client Components |
---|---|
Data fetching | Interactive UI (forms) |
Non-interactive content | State management (useState) |
Accessing backend resources | Browser APIs (localStorage) |
Large dependencies | Context providers |
Solution: Add the use client
Directive
Basic Usage
To fix the error, convert your component to a Client Component by adding 'use client'
as the first line:
'use client'; // Converts this to a Client Component
import { useState } from "react";
export default function Card() {
const [state, setState] = useState(""); // Now works correctly
return <></>;
}
Key Rules
- Top of file: Must be the very first line (above imports)
- Parent components: All parents needing client features must be marked
- Imports: Child components inherit the client boundary
Component Boundaries
The 'use client'
directive creates a "client boundary" - all components imported below it become Client Components automatically
Advanced Patterns
Handling Third-Party Libraries
For libraries not marked with 'use client'
, create a wrapper:
'use client';
export * from '@mui/material'; // Re-export with client boundary
import { Button } from '../lib/mui'; // Now safe to use in Server Components
export default function Page() {
return <Button variant="contained">Submit</Button>;
}
Using Context Providers
Client-only context requires its own boundary:
'use client';
import { createContext } from 'react';
export const ThemeContext = createContext('');
export default function ThemeProvider({ children }) {
return (
<ThemeContext.Provider value="dark">
{children}
</ThemeContext.Provider>
);
}
import ThemeProvider from './theme-provider';
export default function RootLayout({ children }) {
return (
<html>
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
);
}
Initializing Stores with Server Data
Pass server-fetched data to client stores:
async function Layout({ children }) {
const data = await fetchData(); // Server-side fetch
return <PopulateStore data={data}>{children}</PopulateStore>;
}
'use client';
export default function PopulateStore({ data, children }) {
// Initialize store with server data
useStore.setState({ data });
return <>{children}</>;
}
Server Component Data Patterns
Sharing Data Between Server Components
Instead of client state, use module-level singletons:
export const db = new DatabaseConnection(); // Shared instance
import { db } from '@utils/database';
export default async function UsersPage() {
const users = await db.query(); // Reuses connection
return /* ... */;
}
Automatic Optimization
Next.js automatically dedupes fetch()
requests in Server Components:
// These identical calls get deduped
const user = fetch('/api/user');
const posts = fetch('/api/user');
Best Practices
Default to Server Components
- Maximize server rendering benefits
- Only convert interactivity to Client Components
Isolate Client Dependencies
- Create client wrapper files for libraries
- Minimize client-only code in shared components
Layout Optimization
- Keep interactive elements isolated
- Render shared layouts server-side when possible
Data Passing
- Serialize server data for client initialization
- Avoid hydrating entire component trees unnecessarily
Conclusion
The 'use client'
directive is Next.js' mechanism for opting into client-side interactivity in the app router architecture. By understanding:
- The server/client component distinction
- Proper placement of the directive
- Patterns for third-party libraries and context
- Server-side data sharing techniques
You can eliminate the useState
import error while optimizing your application structure. Balance server rendering benefits with essential client interactivity by strategically applying client boundaries only where needed.
Anti-Pattern
Avoid converting entire routes to client components - this removes all server rendering benefits for that route