Server Components vs Client Components in Next.js — when to use which

Understand the difference between Server and Client Components in Next.js 15. Learn when to add 'use client', what breaks if you don't, and see clear examples for each pattern.

The simple rule

In Next.js 15, every component is a Server Component by default. It runs on the server, sends HTML to the browser, and ships zero JavaScript for that component. If you need interactivity (clicks, state, effects), you add "use client" at the top — now it's a Client Component.


Server Component example

This component fetches data from the database and renders it. No "use client" needed:

Server Component
// app/dashboard/page.tsx — Server Component (default)
import { createClient } from "@/libs/supabase/server";

export default async function DashboardPage() {
  const supabase = await createClient();
  const { data: { user } } = await supabase.auth.getUser();

  return (
    <div>
      <h1>Welcome, {user?.email}</h1>
      <p>This page was rendered on the server.</p>
    </div>
  );
}

Key features: you can use async/await, access the database directly, and read secrets from process.env. None of this code goes to the browser.


Client Component example

This component needs a click handler and state — so we add "use client":

Client Component
// components/Counter.tsx — Client Component
"use client";

import { useState } from "react";

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

  return (
    <button onClick={() => setCount(count + 1)}>
      Clicked {count} times
    </button>
  );
}

Client Components run in the browser. They can use useState, useEffect, onClick, and other browser APIs.


Quick decision guide

Decision guide
// Use SERVER component when:
// ✅ Fetching data from database
// ✅ Reading environment variables (secrets)
// ✅ Rendering static content
// ✅ SEO-critical content (faster, less JS)

// Use CLIENT component when:
// ✅ onClick, onChange, onSubmit handlers
// ✅ useState, useEffect, useRef
// ✅ Browser APIs (localStorage, window)
// ✅ Third-party libraries that need the browser

Common mistake: mixing them wrong

You cannot use useState in a Server Component — it will crash. You also cannot use await at the top level in a Client Component. If you need both (e.g., fetch data AND handle clicks), split them:

The pattern: Server fetches, Client interacts
// app/page.tsx — Server Component (fetches data)
import Counter from "@/components/Counter";

export default async function Page() {
  // Fetch on the server
  const data = await getData();

  return (
    <div>
      <h1>{data.title}</h1>
      {/* Pass data down to Client Component */}
      <Counter initialCount={data.count} />
    </div>
  );
}

This is the recommended pattern: Server Components own the data, Client Components own the interactivity. Keep the "use client" boundary as low as possible in your component tree.


Best practices built in

Delfy uses Server Components by default and only adds 'use client' where needed. The codebase is a living example of how to structure a Next.js 15 app.

See the code
Server Components vs Client Components in Next.js — when to use which | Delfy.dev Blog