Skip to content

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:

jsx
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 ComponentsClient Components
Data fetchingInteractive UI (forms)
Non-interactive contentState management (useState)
Accessing backend resourcesBrowser APIs (localStorage)
Large dependenciesContext 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:

jsx
'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

  1. Top of file: Must be the very first line (above imports)
  2. Parent components: All parents needing client features must be marked
  3. 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:

js
'use client'; 

export * from '@mui/material'; // Re-export with client boundary
js
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:

ts
'use client';

import { createContext } from 'react';

export const ThemeContext = createContext('');
export default function ThemeProvider({ children }) {
  return (
    <ThemeContext.Provider value="dark">
      {children}
    </ThemeContext.Provider>
  );
}
js
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:

js
async function Layout({ children }) {
  const data = await fetchData(); // Server-side fetch
  return <PopulateStore data={data}>{children}</PopulateStore>;
}
js
'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:

js
export const db = new DatabaseConnection(); // Shared instance
js
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:

js
// These identical calls get deduped
const user = fetch('/api/user');
const posts = fetch('/api/user');

Best Practices

  1. Default to Server Components

    • Maximize server rendering benefits
    • Only convert interactivity to Client Components
  2. Isolate Client Dependencies

    • Create client wrapper files for libraries
    • Minimize client-only code in shared components
  3. Layout Optimization

    • Keep interactive elements isolated
    • Render shared layouts server-side when possible
  4. 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:

  1. The server/client component distinction
  2. Proper placement of the directive
  3. Patterns for third-party libraries and context
  4. 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