Server Components vs Client Components, Without Jargon

Server components vs client components in Next.js, explained for founders: what each one does, why the split matters for speed and cost, and the mistakes to avoid.

· Justin Boggs

A close-up of a network switch with many cables connected to it

Photo by Albert Stoynov on Unsplash

A server component is a piece of your app that's built on the server and sent to the browser as finished HTML, shipping no JavaScript. A client component is an interactive piece that runs in the browser so it can respond to clicks, typing, and state. In a modern Next.js app, everything is a server component by default, and you opt specific pieces into being client components only when they need to be interactive. That one distinction drives how fast your app loads, how much it costs to run, and where a whole category of bugs comes from. You don't need to write this code by hand to make good calls about it — you need the mental model, and that's what this post gives you.

TL;DR

  • Server components render on the server and send zero JavaScript to the browser. Great for content, data fetching, and anything static.
  • Client components run in the browser. You need them for interactivity: buttons that do things, forms, anything with state.
  • In Next.js, components are server components by default. You add the line 'use client' to turn one into a client component.
  • The practical win is less JavaScript shipped, which means faster loads and lower cost. Push the 'use client' line as far down your component tree as you can.
  • The most common mistake is marking a whole page 'use client' because one button needed it, which drags everything to the browser.

What server and client actually mean here

Before the two component types make sense, the two words in front of them have to. In web apps, the client is the browser on your user's device, and the server is the computer in a data center that holds your app's code and answers requests. That's it. When someone visits your site, their browser (the client) asks your server for the page, and the server does some work and sends a response back.

Next.js leans on this split from the official React Foundations docs: each environment "has its own set of capabilities and constraints." The server is close to your database and your secrets, and it's powerful. The browser is where interactivity lives, because that's where the user actually is, clicking and typing.

The insight the whole modern framework is built on: the code for a feature doesn't all have to run in the same place. Fetching a list of blog posts is best done on the server, next to the database. Making a "like" button count up when tapped has to happen in the browser, next to the user. Older React ran everything in the browser whether it needed to be there or not. The server/client component split lets you put each piece where it belongs.

There's an imaginary line between these two worlds that React calls the network boundary — the point where server code stops and browser code starts. The skill you're building in this post is knowing which side of that line each part of your app belongs on. Get it right and your app is fast and cheap. Get it wrong and you either ship a pile of unnecessary JavaScript or try to run browser-only code on the server, which errors.

If you're weighing whether Next.js is even the right framework to plant this flag in, that's a separate decision I've written about in Next.js vs Remix vs Astro. This post assumes you've picked Next and want to understand the model that comes with it.

What a server component does

A server component is a component that renders entirely on the server and sends only the finished HTML to the browser — no JavaScript for that component travels down the wire. The React docs and Next.js are explicit about this: the result is "dramatically smaller JavaScript bundles" and the ability to "directly access databases, file systems, and secrets, without any API layer."

Read that last part again, because it's the quiet superpower. A server component can talk straight to your database. There's no separate API to build, no fetch call, no loading spinner to manage. The component asks the database for data while it's rendering on the server, gets it, and bakes the result into the HTML it sends over. For a solo founder, that's a whole category of plumbing you simply don't have to build.

Here's what belongs in a server component:

  • Content and layout. Your marketing copy, headings, the structure of a page. Static things.
  • Data fetching. Reading rows from your database, calling an external API, loading a file. Done server-side, next to the source.
  • Anything with secrets. API keys and tokens stay on the server and never reach the browser, which is also a security win.
  • The non-interactive scaffolding that most of any page actually is.

The performance argument is straightforward. Every kilobyte of JavaScript you send has to be downloaded, parsed, and executed on your user's phone before the page becomes usable. Server components send none, so there's nothing to download for those parts. The page shows up as HTML almost immediately. This is the same "ship less, spend less" logic behind choosing boring, proven infrastructure over clever complexity: the cheapest code to run is the code you never send.

One thing a server component cannot do: respond to the user. It has no onClick, no state that changes, no access to the browser. It's a printout, not a control panel. The moment you need the user to do something and see a result, you've crossed the network boundary, and you need the other kind of component.

What a client component does

A client component is a component that runs in the user's browser, which is what lets it be interactive. It can hold state that changes, respond to clicks and keystrokes, run timers, and use browser-only features. Anything that reacts to the user in real time is a client component.

In Next.js you create one by adding a single line to the very top of the file: 'use client'. That directive is the whole switch, and it's a core part of React itself — the 'use client' reference on react.dev documents it as the marker that separates server and client module graphs. Without it, the component renders on the server. With it, React sends the component's JavaScript to the browser so it can run there. The official Next.js learn guide walks through the canonical example — a "Like" button that counts up — and the fix for the error you hit is exactly this: move the button into its own file and mark it 'use client'.

What requires a client component:

  • Buttons that do something on the page without a full reload — add to cart, toggle a menu, like a post.
  • Forms with live behavior — validation as you type, a character counter, a multi-step wizard.
  • Anything using state that changes in response to the user (useState, useReducer).
  • Browser APIs — reading the window size, using local storage, geolocation, anything that only exists in a browser.

The mental model that stuck for me: server components are the printed page, client components are the buttons and switches you place on top of it. Most of any screen is print — content that just needs to be shown. The interactive parts are usually small islands within it. A blog post is a server component; the comment box at the bottom is a client component. A pricing page is a server component; the monthly/annual toggle is a client component.

That framing matters because of cost, and not just page-load cost. Client components are where subtle bugs live — the state that gets out of sync, the effect that runs at the wrong time, the "this works on my machine" browser quirk. The fewer of them you have, the less surface area there is for that class of problem. So the goal isn't to avoid client components; you can't build a real app without them. The goal is to keep them small and specific, which the next section is about.

Putting them together: the boundary is a tree

The reason people trip on this is that server and client components aren't separate apps — they're mixed together in one component tree, and where you draw the line between them decides your performance.

React splits your app into two graphs behind the scenes. The Next.js docs describe it cleanly: a server module tree holds everything rendered on the server, and a client module tree holds every client component. After the server renders, it sends down a compact format called the RSC payload — the finished server HTML plus placeholders marking where the interactive client pieces slot in.

The picture to hold in your head looks like this:

flowchart TD
    L["Layout (server)"] --> N["Nav (server)"]
    L --> P["Page (server)"]
    L --> F["Footer (server)"]
    P --> Posts["Posts list (server, fetches data)"]
    P --> Like["LikeButton (client, 'use client')"]
    Posts --> Like2["LikeButton (client, 'use client')"]

Almost everything is a server component. The interactive LikeButton is a small client island, marked with 'use client', dropped into an otherwise server-rendered page. That's the pattern you're aiming for: a mostly-server tree with client components as leaves.

Here's the rule that follows from it, and it's the single most useful thing in this post: push the 'use client' line as far down the tree as you can. A directive at the top of a file makes that component and everything it renders a client component. So if you slap 'use client' on a whole page because one button needed it, you've just dragged the entire page — content, layout, all of it — into the browser as JavaScript. The right move is to pull the button out into its own tiny file, mark only that file, and leave the page on the server.

| | Server component | Client component | | --- | --- | --- | | Where it runs | On the server | In the browser | | JavaScript shipped | None | Yes | | Can fetch data directly | Yes (database, files, secrets) | No — needs an API call | | Can be interactive | No | Yes (clicks, state, forms) | | Next.js default | Yes | No — opt in with 'use client' | | Use it for | Content, layout, data fetching | Buttons, forms, anything live |

If you can read that table and correctly guess which kind of component a given piece of your app should be, you've got the model. The rest is practice.

The mistakes that cost founders speed

Knowing the two types isn't enough; the value is in avoiding the specific traps. These are the ones I've watched non-technical founders (myself included) fall into.

Marking everything 'use client' out of caution. This is the big one. When an error mentions the server, the tempting fix is to make the whole file a client component and move on. It works, and it quietly ships your entire app to the browser as JavaScript, erasing the performance win that made you pick this framework. The habit to build: when you hit a "this needs to be a client component" error, ask what exactly needs it, and mark only that. Usually it's one button, not the page.

Trying to fetch data in a client component the hard way. Client components can't talk to your database directly. So founders sometimes build an entire API layer to feed data to a client component that didn't need to be a client component in the first place. Nine times out of ten, the fix is to fetch the data in a server component and pass it down, deleting the API layer entirely. This connects to the broader REST vs GraphQL vs tRPC question — server components let you skip a lot of that plumbing for your own app's internal data.

Putting secrets in a client component. Anything in a client component ships to the browser, where a curious user can read it. An API key that lands in a client component is a leaked API key. Keep anything sensitive in server components, always. This is a real security line, not a style preference.

Forgetting that client components still render on the server first. A common confusion: 'use client' doesn't mean "only in the browser." Next.js still renders that component's initial HTML on the server so the page isn't blank, then "hydrates" it in the browser to make it interactive. You don't have to manage this, but knowing it explains why browser-only code (like reading window) needs a guard — on the first server render, there's no window yet.

The thread through all four: the browser is the expensive, error-prone place, and the framework's whole design is nudging you to keep work on the server where it's cheaper and safer. The hidden costs of infrastructure show up in compute bills too, and shipping less JavaScript is one of the few optimizations that helps your load time and your costs at the same time.

Frequently asked questions

Do I need to memorize when to use server vs client components?

No. You need one rule and a fallback. The rule: default to a server component; reach for a client component only when a piece needs to be interactive. The fallback: if you get an error saying something needs to run in the browser (like useState), that specific piece becomes a client component. Your AI assistant handles the syntax — you just need to sanity-check that it isn't making a whole page a client component when one button would do.

What does the 'use client' line actually do?

It's a directive you put at the top of a file that tells React to send that component's JavaScript to the browser and run it there, making it interactive. Without it, the component renders on the server and ships as plain HTML. It also applies to everything that component renders, which is why you keep it on small, leaf-level files rather than top-level pages.

Is a server component the same as server-side rendering (SSR)?

No, and this trips people up. Old-style SSR renders your page on the server but then still ships all the JavaScript and re-runs ("hydrates") the whole thing in the browser. A server component ships no JavaScript for that component at all — the server-rendered HTML is the final product. Server components reduce the browser's work in a way plain SSR doesn't.

Will using more server components make my app faster?

Usually, yes, because they ship less JavaScript for the browser to download and run, which improves load time and time-to-interactive. But the real goal is putting each piece where it belongs, not maximizing one type. A snappy app is mostly server components with small, well-placed client islands for the interactive parts.

Can a server component contain a client component?

Yes, and that's the normal, recommended pattern: a server component page that renders a small client component for its interactive bits. The reverse — importing a server component into a client component — doesn't work the same way and needs a specific pattern (passing it as a child). For most founder apps, "server page, client leaves" covers what you need.

Where to start

The next time you or your AI adds a 'use client' line, ask one question: does this whole file need to be interactive, or just one piece of it? If it's one piece, pull that piece into its own small component and mark only that. That single habit keeps your app mostly server-rendered, which is where the speed and the savings come from. You don't have to write the code to enforce the boundary — you just have to notice when it's drawn in the wrong place.

This server-first model is baked into the way Coding Capybaras is built — the free boilerplate I put together for non-technical founders shipping SaaS with AI coding tools, on the same Next.js and Supabase stack this post describes, with the server/client boundaries already drawn the right way so you can learn from a working example instead of a blank page.