Client Components vs Server Components in Next.js

11 min read👁️ 18
Dharmendra Singh Yadav

Dharmendra Singh Yadav

Founder, Dharmsy Innovations

nextjs-client-components-vs-server-components-explained-simply

nextjs-client-components-vs-server-components-explained-simply

// default: server component
// or:
"use client";

And then the confusion starts:

  1. When should I use Client Components?
  2. When should I use Server Components?
  3. What actually runs on the browser vs the server?
  4. Will I break SEO or performance if I choose wrong?

Let’s clear this up in simple language, with a mental model you can reuse in any project.

1. First idea: Think in terms of

“Where does this code run?”

  1. Server Component → runs on the server.
  2. Client Component → runs in the browser.

That’s the core idea.

In Next.js App Router:

  1. By default, every component is a Server Component.
  2. If you add "use client" at the top of a file, that file becomes a Client Component.

2. What is a Server Component (in human terms)?

A Server Component is a React component that:

  1. Runs only on the server
  2. Can safely access:
  3. Database
  4. Backend APIs
  5. Secrets (keys, tokens)
  6. Does not ship its JS logic to the browser
  7. Can render HTML and send it to the client

You can think of it as:

“React component that behaves like a smart template on the server.”

Key properties of Server Components

  1. ✅ Great for SEO (HTML is rendered on the server)
  2. ✅ Great for performance (less JavaScript sent to client)
  3. ✅ Can do data fetching directly (no need for useEffect or fetch in the client)
  4. ❌ Cannot use browser-only APIs (window, document, localStorage, etc.)
  5. ❌ Cannot use React hooks that depend on the browser (useState, useEffect, useRef for DOM stuff)

3. What is a Client Component (in human terms)?

A Client Component is a React component that:

  1. Runs in the user’s browser
  2. Supports all the typical React stuff:
  3. useState, useEffect, useRef
  4. Event handlers like onClick, onChange, etc.
  5. Browser APIs like window, document, localStorage
  6. Needs "use client" at the top of the file

You can think of it as:

“Interactive widgets that live in the browser — buttons, forms, dropdowns, modals, etc.”

Key properties of Client Components

  1. ✅ Needed for buttons, dropdowns, forms, modals, animations
  2. ✅ Needed for browser APIs (local storage, cookies on client, scroll events)
  3. ❌ More JavaScript is shipped to the browser
  4. ❌ Harder to keep ultra-fast if everything is client-side

4. The golden rule:

“Most of your tree should be Server Components.

Only make interactive parts Client Components.”

In Next.js App Router, best practice is:


Use Server Components by default.
Drop into Client Components only when you need interactivity.


So your page usually looks like:

  1. Layout → Server
  2. Page → Server
  3. Big sections → Server
  4. Small UI widgets inside → Client

Example mental model:

// app/page.tsx (Server Component by default)
import Hero from "./Hero"; // Server
import Features from "./Features"; // Server
import ContactForm from "./ContactForm"; // Client

export default function HomePage() {
return (
<>
<Hero />
<Features />
<ContactForm /> {/* Only this is client-side */}
</>
);
}

In this example:

  1. The overall page is rendered on the server for performance and SEO.
  2. Only the ContactForm (which needs form state, validation, maybe live feedback) is a Client Component.

5. How do you make a Client Component?

Simple: add "use client" at the top of the file.

// app/ContactForm.tsx
"use client";

import { useState } from "react";

export default function ContactForm() {
const [email, setEmail] = useState("");
const [message, setMessage] = useState("");

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
// send data to API route or server action
};

return (
<form onSubmit={handleSubmit}>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Your email"
/>
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Your message"
/>
<button type="submit">Send</button>
</form>
);
}

This file is compiled for the browser and gets hydrated on the client.

6. Data fetching: where should it happen?

With Server Components

You can fetch data directly at the top level of your component:

// app/users/page.tsx (Server Component)
import { getUsers } from "@/lib/db";

export default async function UsersPage() {
const users = await getUsers(); // direct DB call

return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}

This code:

  1. Runs on the server
  2. Can talk directly to your database or internal APIs
  3. Sends fully rendered HTML to the browser

No useEffect, no loading spinners unless you want them.

With Client Components

If you fetch inside a Client Component, it behaves like classic React:

  1. You use useEffect, fetch, axios, etc.
  2. Data is fetched from the browser
  3. You might get loading states and spinners

Best practice: fetch data in Server Components, then pass it down to Client Components as props.

Example:

// app/dashboard/page.tsx (Server)
import DashboardChart from "./DashboardChart";

export default async function DashboardPage() {
const stats = await getDashboardStats(); // DB or API

return <DashboardChart stats={stats} />; // pass as props
}


// app/dashboard/DashboardChart.tsx (Client)
"use client";

import { useState } from "react";

export default function DashboardChart({ stats }) {
const [selected, setSelected] = useState("week");

// Use stats prop + local state for chart filters
return (
<div>
{/* interactive UI */}
</div>
);
}

7. What can’t you do in a Server Component?

Things that require the browser:

  1. useState, useEffect, useLayoutEffect
  2. Event handlers like onClick, onChange, etc.
  3. (you CAN write them in JSX, but they won’t work unless the component is client-side)
  4. Accessing:
  5. window
  6. document
  7. localStorage
  8. DOM APIs
  9. Using client-side libraries like:
  10. Chart libraries designed for the browser
  11. framer-motion
  12. react-hook-form (usually)
  13. Most UI libraries that expect a browser environment

If you need any of these → move that component to Client with "use client".


8. Can a Client Component import a Server Component?



No.

  1. A Server Component can import a Client Component.
  2. But a Client Component cannot import a Server Component.

Why?

Because Server Components may contain server-only code (DB calls, secrets). That can’t be bundled into browser JS.

So the direction is always:

Server → Client (allowed)
Client → Server (not allowed)

If you accidentally try to import a Server Component into a Client Component, Next.js will complain.

9. Common patterns (with examples)

Pattern 1: Server page, client widget

// app/product/[id]/page.tsx (Server)
import BuyButton from "./BuyButton";

export default async function ProductPage({ params }) {
const product = await getProduct(params.id);

return (
<div>
<h1>{product.title}</h1>
<p>{product.price}</p>
<BuyButton productId={product.id} /> {/* interactive */}
</div>
);
}


// app/product/[id]/BuyButton.tsx (Client)
"use client";

export default function BuyButton({ productId }) {
const handleClick = () => {
// call server action or API route
};

return <button onClick={handleClick}>Buy Now</button>;
}

Pattern 2: Client layout section only

Sometimes you want sticky nav with scroll effects or theme toggles.

// app/layout.tsx (Server)
import Navbar from "./Navbar";

export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<Navbar />
{children}
</body>
</html>
);
}


// app/Navbar.tsx (Client)
"use client";

import { useState, useEffect } from "react";

export default function Navbar() {
const [scrolled, setScrolled] = useState(false);

useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 20);
window.addEventListener("scroll", onScroll);
return () => window.removeEventListener("scroll", onScroll);
}, []);

return (
<nav className={scrolled ? "nav nav--shadow" : "nav"}>
My App
</nav>
);
}

10. Performance: why Server Components are a big deal

Next.js pushes you towards Server Components because they solve a big problem:

too much JavaScript in the browser.

Benefits of Server Components:

  1. No component logic shipped to client
  2. Can stream HTML to the browser
  3. Easier to keep bundle size small
  4. Can do heavy work on the server (queries, data merging, auth, etc.)

Compare:

  1. Classic SPA → large JS bundle, more hydration work
  2. RSC + Next.js → only interactive parts hydrate on client

So a good rule:

If your component doesn’t need interactivity, keep it as a Server Component.

11. Cheat sheet: When to pick what?

Use a Server Component when:

  1. You are rendering static or data-driven UI
  2. You need to query a database, CMS, or external API
  3. You care about SEO
  4. You don’t need useState / useEffect
  5. The component is mostly just layout / content

Examples:

  1. Blog pages
  2. Product details
  3. Dashboard shell
  4. Static marketing sections
  5. Navigation that doesn’t need scroll/hover JS tricks

Use a Client Component when:

  1. You need state (useState)
  2. You need side effects (useEffect)
  3. You are handling events (onClick, onChange, etc.)
  4. You use browser APIs (window, document, localStorage)
  5. You use interactive libraries (charts, modals, sliders, forms)

Examples:

  1. Forms with validation and live feedback
  2. Theme toggle (dark/light)
  3. Menus, dropdowns, accordions
  4. Drag-and-drop interfaces
  5. Complex chart components
  6. Chat input box

12. Migrating your thinking (if you’re coming from CRA / older Next.js)

If you’re used to “everything is a client component”, this will feel weird at first.

Old mindset:

Fetch in useEffect → set state → render.

New mindset (Next.js App Router):

Fetch on the server → pass data into components → add client islands only where absolutely needed.

So instead of:

"use client";
export default function Page() {
const [data, setData] = useState([]);

useEffect(() => {
fetch("/api/data").then((res) => res.json()).then(setData);
}, []);

return <UI data={data} />;
}

You now do:

// Server
export default async function Page() {
const data = await getData();
return <UI data={data} />; // UI can be server or client
}

This small shift gives you huge performance wins.

13. Final mental model (keep this in your head)

When you’re confused, ask these questions:

  1. Does this component need interactivity or browser APIs?
  2. Yes → Client Component
  3. No → Server Component
  4. Can I fetch data on the server instead of the client?
  5. Yes → Do it in a Server Component
  6. Can I keep most of the page server-side and only make small pieces client-side?
  7. Yes → That’s usually the best option.

14. Short recap

  1. Next.js App Router uses Server Components by default.
  2. Add "use client" at the top to make a Client Component.
  3. Server Components:
  4. Run only on server
  5. Great for data fetching & SEO
  6. No interactivity directly
  7. Client Components:
  8. Run in browser
  9. Needed for state, effects, events & browser APIs
  10. Best practice:
“Keep most of your UI as Server Components.
Use Client Components only for interactive islands.”

Once this clicks, Next.js suddenly feels much simpler — and your app becomes faster almost for free.

Work with Dharmsy Innovations

Turn Your SaaS or App Idea Into a Real Product — Faster & Affordable

Dharmsy Innovations helps founders and businesses turn ideas into production-ready products — from MVP and prototypes to scalable platforms in web, mobile, and AI.

No sales pressure — just honest guidance on cost, timeline & tech stack.