TechLead

Server & Client Components

Understand React Server Components and when to use client components

React Server Components (RSC)

Next.js uses React Server Components by default in the App Router. Server Components render on the server, reduce client-side JavaScript, and can directly access backend resources like databases and file systems.

πŸ–₯️ Server Components (Default)

  • βœ… Fetch data directly (no useEffect)
  • βœ… Access backend resources (DB, filesystem)
  • βœ… Keep sensitive info on server (API keys)
  • βœ… Reduce client bundle size
  • ❌ Cannot use hooks (useState, useEffect)
  • ❌ Cannot use browser APIs
  • ❌ Cannot add event listeners

πŸ’» Client Components ('use client')

  • βœ… Use React hooks (useState, useEffect)
  • βœ… Add event listeners (onClick, onChange)
  • βœ… Access browser APIs (localStorage, window)
  • βœ… Use third-party hooks libraries
  • ❌ Cannot be async functions
  • ❌ Increase client bundle size

Server Component Example

Server Components can be async, fetch data directly, and keep secrets on the server while returning only HTML to the client.

// app/users/page.tsx - Server Component (default)
// No 'use client' directive needed

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

// This component runs only on the server
export default async function UsersPage() {
  // Direct database access - no API needed!
  const users = await db.user.findMany();
  
  // Async/await works directly in the component
  const stats = await fetch('https://api.example.com/stats', {
    headers: {
      // Safe to use - never sent to client
      Authorization: `Bearer ${process.env.SECRET_API_KEY}`,
    },
  }).then(res => res.json());

  return (
    <div>
      <h1>Users ({stats.total})</h1>
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

// Zero client-side JavaScript for this component!

Client Component Example

Client Components opt into browser APIs and hooks, so they’re ideal for interactive UI and stateful behavior.

'use client'; // This directive makes it a Client Component

import { useState, useEffect } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  const [mounted, setMounted] = useState(false);

  // useEffect works in Client Components
  useEffect(() => {
    setMounted(true);
    // Access browser APIs
    const saved = localStorage.getItem('count');
    if (saved) setCount(parseInt(saved));
  }, []);

  const increment = () => {
    setCount(c => c + 1);
    localStorage.setItem('count', String(count + 1));
  };

  if (!mounted) return null; // Avoid hydration mismatch

  return (
    <button onClick={increment}>
      Count: {count}
    </button>
  );
}

Composition Patterns

Prefer wrapping Server Components with a small Client Component to keep most of the tree server-rendered and avoid bloating the client bundle.

// βœ… CORRECT: Pass Server Components as children
// app/dashboard/page.tsx (Server Component)
import ClientWrapper from './ClientWrapper';
import ServerData from './ServerData';

export default async function Dashboard() {
  return (
    <ClientWrapper>
      {/* ServerData stays a Server Component */}
      <ServerData />
    </ClientWrapper>
  );
}

// ClientWrapper.tsx
'use client';
export default function ClientWrapper({ 
  children 
}: { 
  children: React.ReactNode 
}) {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <div>
      <button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
      {isOpen && children}
    </div>
  );
}

// ❌ WRONG: Importing Server Component into Client Component
'use client';
import ServerComponent from './ServerComponent'; // This becomes a Client Component!

When to Use Each

Use Case Component Type
Fetch data πŸ–₯️ Server
Access backend resources πŸ–₯️ Server
Keep sensitive data secure πŸ–₯️ Server
Add interactivity (onClick, onChange) πŸ’» Client
Use state (useState, useReducer) πŸ’» Client
Use lifecycle effects (useEffect) πŸ’» Client
Use browser-only APIs πŸ’» Client
Use React Context πŸ’» Client

Mixing Components

Keep client interactivity at the leaves so data-heavy components remain server-rendered.

// Best Practice: Keep Client Components at the leaves

// ❌ BAD: Large Client Component wrapping everything
'use client';
export default function Page() {
  const [filter, setFilter] = useState('');
  return (
    <div>
      <input onChange={e => setFilter(e.target.value)} />
      <DataTable filter={filter} /> {/* Now forced to be client */}
    </div>
  );
}

// βœ… GOOD: Small Client Component for interactivity only
// SearchFilter.tsx
'use client';
export function SearchFilter({ onFilter }: { onFilter: (v: string) => void }) {
  return <input onChange={e => onFilter(e.target.value)} />;
}

// page.tsx (Server Component)
import { SearchFilter } from './SearchFilter';
import { DataTable } from './DataTable'; // Stays Server Component

export default async function Page() {
  const data = await fetchData();
  return (
    <div>
      <SearchFilter onFilter={handleFilter} />
      <DataTable data={data} />
    </div>
  );
}

Third-Party Libraries

Many UI libraries depend on hooks, so wrap them in a small Client Component and pass data from the server.

// Many libraries use hooks and need 'use client'
// Create wrapper components for third-party components

// components/ChartWrapper.tsx
'use client';

import { LineChart } from 'some-chart-library';

export function ChartWrapper({ data }: { data: number[] }) {
  return <LineChart data={data} />;
}

// Use in Server Component
import { ChartWrapper } from './ChartWrapper';

export default async function Dashboard() {
  const data = await fetchChartData(); // Server-side fetch
  return <ChartWrapper data={data} />; // Client-side render
}

πŸ“– Server Components Documentation β†’

⚑ Key Takeaways

  • β€’ Default to Server Components - only use 'use client' when needed
  • β€’ Keep Client Components small and at the leaf nodes
  • β€’ Pass Server Components as children to Client Components
  • β€’ Don't import Server Components into Client Components
  • β€’ Wrap third-party libraries that need hooks