When to Use Each

Default to Server Components; reach for Client Components only where you need interactivity.

A simple rule of thumb: start on the server, move to the client only when you must.

Use a Server Component when you...

  • Fetch data.
  • Access secrets, databases, or the file system.
  • Render mostly static content.

Use a Client Component when you...

  • Need useState or useEffect.
  • Handle events like onClick or onChange.
  • Use browser-only APIs such as localStorage.
NeedComponent type
Fetch data / DB accessServer
State & interactivityClient
Reduce JS shippedServer

Example

Example · typescript
// Data fetching stays on the server; only the toggle is a client island.
import ThemeToggle from './theme-toggle'; // 'use client'

export default async function Header() {
  const user = await getUser(); // server-side
  return (
    <header>
      <span>Hi, {user.name}</span>
      <ThemeToggle />
    </header>
  );
}

When to use it

  • A developer defaults all new page components to Server Components for better SEO and smaller bundles.
  • A form validation component is marked 'use client' only because it needs onChange handlers and useState.
  • A data-heavy report page keeps the data table as a Server Component but extracts an export button as a Client Component.

More examples

Server: no interactivity needed

No event handlers or hooks are needed so this stays a Server Component, reducing bundle size.

Example · ts
// app/blog/page.tsx  — Server Component (default)
import { getPosts } from '@/lib/posts';

export default async function BlogPage() {
  const posts = await getPosts();
  return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}

Client: needs browser event

The toggle behaviour requires state and a click handler, so 'use client' is necessary here.

Example · ts
'use client';
import { useState } from 'react';

export default function Accordion({ title, body }: { title: string; body: string }) {
  const [open, setOpen] = useState(false);
  return (
    <div>
      <button onClick={() => setOpen(o => !o)}>{title}</button>
      {open && <p>{body}</p>}
    </div>
  );
}

Hybrid: server page + client widget

Composing both types in one page keeps the heavy data-table server-rendered while the interactive button ships as client code.

Example · ts
// app/dashboard/page.tsx
import StatsTable from './StatsTable';    // Server Component
import ExportButton from './ExportButton'; // 'use client'

export default async function DashboardPage() {
  const stats = await fetchStats();
  return (
    <>
      <StatsTable data={stats} />
      <ExportButton rows={stats} />
    </>
  );
}

Discussion

  • Be the first to comment on this lesson.