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:
// 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":
// 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
// 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 browserCommon 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:
// 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