Skip to content
tezvyn:

Server vs. Client Components in Next.js

MediumHow cards are made

Next.js forces a render boundary. Server Components generate HTML without sending JavaScript, while Client Components ship JS for interactivity. The footgun is importing a heavy library into a Server Component via a child, bloating the client bundle.

Why it exists

Before the App Router, every React component shipped to the browser, even if it only displayed static data fetched from a database. That meant larger JavaScript bundles, slower hydration, and API keys or database queries exposed only by virtue of needing client-side code to render. Next.js needed a way to keep React's component model while letting parts of the tree run exclusively on the server, sending only HTML to the browser.

The mental model

Think of your UI as a document assembled in two different factories. The server factory builds the structural frame, fetches data, and paints the initial picture using zero browser code. The client factory adds the moving parts: buttons that click, forms that validate, animations that respond to the mouse. Every component must choose its factory before it ships. A server-built piece can contain client-built pieces inside it, but once a subtree crosses into the client factory, everything inside that subtree is assumed to need the browser.

How it works

Server Components render during the request on the server. They can read files, query a database, and import server-only modules. The output is a JSON-like stream that the Next.js runtime uses to build HTML and merge it with Client Component placeholders. Client Components are pre-rendered on the server for the initial HTML, but their JavaScript is also sent to the browser so React can hydrate them and attach event listeners. The boundary is created by the use client directive at the top of a file. Any component imported into a file marked with use client automatically becomes part of the client bundle, even if its own file has no directive.

When to use it

Use Server Components for layouts, pages that read from a database, static content, and any UI that does not need event listeners or browser APIs. They reduce bundle size and keep sensitive logic on the server. Use Client Components for interactive elements like date pickers, shopping carts, charts that rely on window size, and anything using useState, useEffect, or browser-only libraries.

When not to use it

Do not use a Client Component for a purely static hero section just because it lives inside an interactive page. Do not fetch sensitive data inside a Client Component if you can fetch it on the server. Avoid marking an entire page as a Client Component when only a small island at the bottom needs interactivity; that forces the whole tree to ship JavaScript unnecessarily.

One canonical example

Imagine a product page. The shell, navigation, and product details are Server Components that fetch inventory and descriptions directly from Postgres. The color selector and Add to Cart button live in a Client Component nested inside the page. The product data flows down as props from the server to the client island. If a developer accidentally imports a large charting library into the Server Component file that is only used inside the Client Component, the bundler still sends that library to the browser because the import graph crosses the use client boundary, silently inflating the bundle.

Interview question

A Server Component shell nests a Client Component chart that uses a heavy library. Which import strategy avoids the bundle-size footgun described in the card?

  • a.Add 'use client' to the shell so the whole page shares the library
  • b.Import the library in the Server Component file and pass processed data to the chart
  • c.Import the library only inside the Client Component file that renders the chartCorrect
  • d.Import the library in a shared module used by both the shell and the chart
Why?

The card warns that importing a heavy library into a Server Component file—even when only a child Client Component uses it—causes the bundler to send that library to the browser, so the import should live inside the Client Component file. Option B exemplifies this exact footgun, while Option A would incorrectly force the entire shell to ship JavaScript.

Just read this? Test yourself on what you have been reading.

Put your scrolling time to good use

Learn one idea, try a quiz and save useful cards for revision. Tezvyn makes it easy to learn and stay current in your tech field, a few minutes at a time.

The iPhone app is on the way

We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.

Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.

Get it on Google PlayiPhone app coming soon

We are hiring for this. Open roles that interview on next.js — each one lists the topics its interview covers.

See open roles