avatar

Le Do Nghiem

Software Engineer

  • About me
  • Books
  • Snippets
  • Blog

© 2026 Le Do Nghiem. All rights reserved.

Contact |

Back to Blog

Understanding 'use client' and 'use server' in Next.js

avatar
Le Do NghiemSoftware Engineer
2025-12-18 5 min read

Introduction

Next.js 13+ introduced the App Router with a revolutionary concept: Server Components by default. Understanding the difference between 'use client' and 'use server' is crucial for building efficient Next.js applications. Let's dive deep into these directives and when to use them.

What are Server Components?

By default, all components in the App Router are Server Components. They render on the server, have access to backend resources directly, and don't send JavaScript to the client.

// app/blog/page.tsx (Server Component by default)
import { db } from '@/lib/db';

export default async function BlogPage() {
  // Direct database access - no API route needed!
  const posts = await db.post.findMany();
  
  return (
    <div>
      {posts.map(post => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  );
}

Benefits:

  • Smaller bundle size (no JavaScript sent to client)
  • Direct database/API access
  • Better security (secrets stay on server)
  • Improved SEO (fully rendered HTML)

When to Use 'use client'

Use 'use client' when your component needs:

1. Browser APIs

'use client';

import { useEffect, useState } from 'react';

export default function WindowSize() {
  const [size, setSize] = useState({ width: 0, height: 0 });
  
  useEffect(() => {
    const handleResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };
    
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  
  return <div>Window: {size.width} x {size.height}</div>;
}

2. Event Handlers

'use client';

export default function Button() {
  const handleClick = () => {
    alert('Button clicked!');
  };
  
  return <button onClick={handleClick}>Click me</button>;
}

3. React Hooks

'use client';

import { useState, useEffect } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

4. Context API

'use client';

import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

When to Use 'use server'

The 'use server' directive is used to create Server Actions - functions that run on the server but can be called from Client Components.

Server Actions

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

import { db } from '@/lib/db';
import { revalidatePath } from 'next/cache';

export async function createPost(formData: FormData) {
  const title = formData.get('title');
  const content = formData.get('content');
  
  await db.post.create({
    data: { title, content }
  });
  
  revalidatePath('/posts');
}

Using Server Actions in Client Components

'use client';

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

export default function PostForm() {
  async function handleSubmit(formData: FormData) {
    await createPost(formData);
  }
  
  return (
    <form action={handleSubmit}>
      <input name="title" placeholder="Title" />
      <textarea name="content" placeholder="Content" />
      <button type="submit">Create Post</button>
    </form>
  );
}

The Component Boundary

The 'use client' directive creates a boundary. Everything imported into a Client Component becomes part of the client bundle.

// ❌ Bad: Unnecessary client boundary
'use client';

import { db } from '@/lib/db'; // This won't work - db is server-only

export default function Component() {
  // Only needs client for interactivity
}
// Good: Minimal client boundary
// Server Component
import { db } from '@/lib/db';
import InteractiveButton from './InteractiveButton';

export default async function Page() {
  const data = await db.query(); // Server-side
  
  return (
    <div>
      <h1>{data.title}</h1>
      <InteractiveButton /> {/* Only this is client-side */}
    </div>
  );
}

// InteractiveButton.tsx
'use client';

export default function InteractiveButton() {
  return <button onClick={() => alert('Clicked')}>Click</button>;
}

Best Practices

1. Keep Client Components Small

// Good: Small, focused client component
'use client';

export default function LikeButton({ postId }) {
  const [liked, setLiked] = useState(false);
  
  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? '❤️' : '🤍'}
    </button>
  );
}

2. Use Server Components for Data Fetching

// Good: Server Component fetches data
export default async function PostList() {
  const posts = await fetchPosts();
  
  return (
    <div>
      {posts.map(post => (
        <PostCard key={post.id} post={post} />
      ))}
    </div>
  );
}

3. Combine Server and Client Components

// Server Component
export default async function Dashboard() {
  const data = await fetchDashboardData();
  
  return (
    <div>
      <ServerStats data={data} />
      <ClientChart data={data} />
    </div>
  );
}

// Client Component
'use client';
import { Chart } from 'chart.js';

export default function ClientChart({ data }) {
  useEffect(() => {
    // Initialize chart
  }, []);
  
  return <canvas id="chart" />;
}

Common Mistakes

Mistake 1: Using 'use client' Unnecessarily

// Bad
'use client';

export default function StaticContent() {
  return <h1>Hello World</h1>;
}

// Good
export default function StaticContent() {
  return <h1>Hello World</h1>;
}

Mistake 2: Trying to Use Browser APIs in Server Components

// Bad
export default function Component() {
  const width = window.innerWidth; // Error: window is not defined
  return <div>{width}</div>;
}

// Good
'use client';

export default function Component() {
  const [width, setWidth] = useState(0);
  useEffect(() => {
    setWidth(window.innerWidth);
  }, []);
  return <div>{width}</div>;
}

Performance Considerations

  • Server Components: Zero JavaScript sent to client (unless imported by Client Component)
  • Client Components: JavaScript is bundled and sent to client
  • Server Actions: No API routes needed, direct server function calls

Conclusion

Understanding 'use client' and 'use server' is essential for building efficient Next.js applications:

  • Default to Server Components - they're faster and more secure
  • Use 'use client' sparingly - only when you need interactivity or browser APIs
  • Use 'use server' for mutations - Server Actions simplify form handling and data mutations

By following these patterns, you'll build faster, more secure, and more maintainable Next.js applications!

Previous Post

JWT vs Session Authentication: Choosing the Right Approach

Next Post

Mastering RxJS in Angular: Reactive Programming Patterns