SyntaxStudy
Sign Up
Next.js Client Components vs Server Components in Pages
Next.js Beginner 1 min read

Client Components vs Server Components in Pages

In the App Router every component is a Server Component by default, meaning it renders on the server and can be async. To make a component a Client Component you add the 'use client' directive as the very first line of the file. Client Components are rendered on the server during the initial page load (for HTML) and then hydrated in the browser, giving you access to browser APIs, event handlers, and React hooks like useState and useEffect. The key architectural principle is to push the 'use client' boundary as far down the component tree as possible. Keep data-fetching and non-interactive UI in Server Components, and only use Client Components for parts that genuinely need interactivity. Server Components can import and render Client Components, but Client Components cannot import Server Components — they can only receive Server Components as props via the children pattern. A common pattern is the "leafs" approach: build large page sections as Server Components that fetch their own data, then pass only the minimal interactive elements (buttons, forms, modals) as Client Components. This maximises the amount of JavaScript that stays on the server, reducing Time to Interactive and improving Core Web Vitals scores.
Example
// app/dashboard/page.tsx — Server Component (no directive needed)
import LikeButton from '@/components/LikeButton'; // Client Component
import { db } from '@/lib/db';

export default async function DashboardPage() {
  // Direct database access — safe because this runs on the server only
  const stats = await db.query('SELECT * FROM user_stats WHERE id = 1');

  return (
    <section>
      <h1>Welcome back</h1>
      <p>Total posts: {stats.postCount}</p>
      {/* Pass data to Client Component as props */}
      <LikeButton initialCount={stats.likes} />
    </section>
  );
}

// components/LikeButton.tsx — Client Component
'use client';

import { useState } from 'react';

export default function LikeButton({ initialCount }: { initialCount: number }) {
  const [count, setCount] = useState(initialCount);
  const [liked, setLiked] = useState(false);

  const handleLike = async () => {
    setLiked(true);
    setCount((c) => c + 1);
    await fetch('/api/like', { method: 'POST' });
  };

  return (
    <button
      onClick={handleLike}
      disabled={liked}
      className="flex items-center gap-2"
    >
      {liked ? 'Liked' : 'Like'} ({count})
    </button>
  );
}