Stop 10 · Full-Stack FrameworksTopics · 2/7

Server Components & Client Components

Part of the Full Stack Developer Roadmap

In short

In Next.js, most components can run only on the server, read straight from the database and send zero JavaScript to the browser. The skill is spotting the few that must be client components.

Next.js's default of rendering components on the server (no client JS shipped) unless explicitly marked `'use client'` — blurring the line between 'frontend' and 'backend' code even further.

The blob character holds a plate of ready dishes beside a live dosa griddle at a wedding buffet.

Think of it like this

Thali vs the live dosa counter

At a wedding, most dishes are cooked in the back kitchen and arrive ready on your plate. The dosa counter is different: it cooks in front of you and changes as you ask, 'extra crispy, less ghee'. Server components are the ready dishes. Client components are the live counter.

Watch it happen

Which parts ship JavaScript?

page.tsx
Header
SearchBox
ProductList
ProductCard
AddToCart
Footer

1/5page.tsx is a server component by default. It can be async and query the database directly.

The big ideas

What you need to know

Server is the default

In the App Router every component is a server component unless its file starts with 'use client'.

Fetch where you render

Server components can be async and read the database or call secret APIs. No useEffect and no extra API route.

Client for interaction

Anything with useState, useEffect, onClick or browser APIs like localStorage must be a client component.

Push 'use client' down

Mark the smallest leaf that needs interactivity. Marking the whole page sends everything it imports to the browser.

See it in code

Walk through the code

tsx
1// app/products/page.tsx: a server component (the default)2import { db } from "@/lib/db";3import { AddToCart } from "./add-to-cart";4export default async function ProductsPage() {5  const products = await db.product.findMany({ take: 20 });6  return <ul>{products.map((p) => <li key={p.id}>{p.name} <AddToCart productId={p.id} /></li>)}</ul>;7}8 9// app/products/add-to-cart.tsx10"use client";11import { useState } from "react";12export function AddToCart({ productId }: { productId: number }) {13  const [added, setAdded] = useState(false);14  return <button onClick={() => setAdded(true)} disabled={added}>{added ? "Added" : "Add to cart"}</button>;15}

1/4The page is async and queries the database right inside the component. That code stays on the server.

Two files, two worlds. A real AddToCart would also send productId to a server action; this one just shows where state lives.

Save yourself the pain

Common mistakes

Oops

Adding 'use client' to page.tsx because one button needs onClick.

Do this instead

Move the button into its own client component and keep the page on the server.

Oops

Importing your database client into a client component.

Do this instead

Keep database and secret code in server files. The server-only package can make such an import fail the build.

Your mission

Split a page into server and client

~30 min

Quick check

Which of these must be a client component?

Go deeper

  1. 1Fetch data directly inside a Server Component with `async`/`await`, no API call needed.
  2. 2Add `'use client'` to a component that needs `useState` or an event handler.
  3. 3Learn to keep the client-component boundary small for better performance.

More topics at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge