Web Applications

Server Components vs Client Components: Choosing Without Guessing

Next.js made Server Components the default, and a lot of developers now sprinkle "use client" wherever something breaks until the error goes away. That works, but it ships far more JavaScript to the browser than necessary. The server vs client components choice isn’t mysterious, there’s a simple rule.

Side-by-side comparison illustration of Server Components versus Client Components, Server Components vs Client Components: Choosing Without Guessing
Server Components vs Client Components: the trade-offs, side by side.

Server Component by default

It renders on the server, sends HTML, and ships zero JavaScript for that component to the browser. It can fetch data directly, access the database, and keep secrets on the server. For anything that just displays data, most of a typical page, this is what you want.

Because the data loading happens on the server, it also makes caching easier. One server-side fetch can serve every visitor, which matters once API bills start to grow. I go into that in reducing API costs in Next.js applications.

Client Component only for browser behavior

Reach for a Client Component when you need state and interactivity (useState, useEffect), event handlers like onClick, or browser APIs like localStorage. If a component needs to respond to user interaction in the moment, it has to run in the browser.

One common misunderstanding is worth clearing up. A Client Component is not rendered only in the browser. On the first load, Next.js still renders it to HTML on the server, then the browser downloads its JavaScript and makes it interactive. So marking something client does not hurt the first paint much. What it costs is the JavaScript the browser must download and run.

The mistake is making large sections client components because one small piece inside them is interactive. Don’t. Keep the big, static parts on the server and push the interactive bit into its own small client component. A button that needs onClick should be a tiny client island inside an otherwise server-rendered page, not a reason to mark the whole page client.

Infographic summarising “Server Components vs Client Components: Choosing Without Guessing”: Server Component by default; Client Component only for browser behavior; Server vs client components: how the boundary works; Where this choice shows up
The article at a glance.

Server vs client components: how the boundary works

"use client" does not mark one component. It marks a boundary. Everything that file imports becomes part of the client bundle too. That is why one directive at the top of a layout can quietly pull a whole section into the browser. The Next.js guide to server and client components covers the details. These are the patterns I lean on:

  • Pass server content as children. A client modal or tab panel can take server-rendered content through children. That content stays on the server and arrives already rendered.
  • Keep context providers deep. A provider needs to be a client component, but it should wrap only the part of the tree that uses it.
  • Wrap third-party widgets. If a library component uses hooks but lacks the directive, wrap it in your own small client file.
  • Pass plain data across the boundary. Props sent to a client component must be serializable, so send values, not class instances or functions.
  • Guard server code. Import server-only in files that hold secrets or database access, so a wrong import fails the build instead of leaking.

A worked example: a product page

Take a typical product page. The header, title, description, specifications and reviews only display data, so they stay as server components. The image gallery needs swipe gestures and state, so it becomes a client component.

The quantity picker and the add to cart button need state and clicks, so they become one small client component. The page itself stays a server component that fetches the product once and passes plain values down. Out of a dozen pieces, two run in the browser.

Common mistakes

  • Marking the root layout client to use one provider, which pulls the whole layout into the bundle.
  • Fetching in useEffect data that a server component could have fetched before the page was sent.
  • Passing a full database record into a client component. Every field is sent to the browser, even the ones the component never shows.
  • Reading secrets in a client file. Environment variables without the NEXT_PUBLIC_ prefix are not available in the browser, and the ones with it are public by design.

Checks you can run

  • Search the project for "use client" and ask, for each file, which line actually needs the browser.
  • Look at the JavaScript a route ships after a production build, and watch it fall as you move static parts back to the server.
  • Check pages with JavaScript disabled. Server-rendered content should still read correctly, even if buttons do nothing.

Where this choice shows up

The same rule fixes the login flash on protected pages. When the auth check lives in a server component, the wrong user never receives the page at all. I explain that in handling auth redirects in Next.js without the flicker.

It is not unique to Next.js, either. Shipping less code to the browser is what fixes most slow storefronts too, as you can see in why your Shopify store is slow. If you are planning an app and want this structure right from the start, that is how I approach web application development.

Ask one question per component: does this need to run in the browser? If no, it’s a Server Component. If yes, isolate exactly the part that does.

Questions people ask

What is the difference between server and client components?

A Server Component renders on the server and sends no JavaScript for itself to the browser. It can fetch data and read secrets directly. A Client Component also renders HTML on the first load, but its JavaScript is sent to the browser so it can handle state, events and browser APIs.

When should I use "use client" in Next.js?

Only in components that need the browser: ones that use state or effects, handle events like clicks, or read browser APIs such as localStorage. Put the directive on the smallest component that needs it, not on a page or layout, because everything that file imports joins the client bundle.

Can a Client Component contain a Server Component?

Not by importing it, because anything imported into a client file becomes client code. You can pass a Server Component to a Client Component as children or another prop. It is rendered on the server and handed over as finished output, so it ships no extra JavaScript.

Are Client Components bad for SEO?

Not directly. On the first load, Next.js renders Client Components to HTML on the server, so search engines can read the content. The real cost is the JavaScript the browser has to download and run, which can slow the page down. Keep client code small and the problem mostly disappears.

Comments

Leave a comment

0 / 3000
Comments are moderated. Links are not clickable and your email is never published. See the privacy policy.
keep reading

Related articles

Usually replies within a day
let’s talk

Tell me what you want to build

A website, an app, some automation, or something you’re still figuring out. Tell me the problem and I’ll give you a straight answer on what it takes. No pressure, no sales team.