Server Components are the default in modern React, and most teams I review have not internalized that yet. Their codebases are mostly 'use client' at the top of every file, the way React was written from 2014 to 2022, and the Server Component features get used as an afterthought, not as the substrate. That ordering is wrong, and unwinding it later is harder than starting from the right default.
The contrarian claim I will defend: in a 2026 Next.js or modern Remix codebase, the right starting position is that every component is a Server Component until it cannot be. You opt in to the client only at the leaves where state, effects, browser APIs, or event handlers are actually needed. Most teams do the opposite, and the cost shows up as bigger bundles, slower first paints, and a confusing data layer where everything calls fetch from useEffect because nobody noticed they could fetch on the server instead.
The mental shift: where the code runs
For about a decade, React components ran in exactly one place: the browser. The server might have done a one-shot hydration render, but the model was "client renders the tree, the server is incidental". Server Components break that assumption. There are now two kinds of component, and they live in different runtimes.
A Server Component runs on the server, once per request (or once per render cache hit), and ships the rendered output to the browser as part of the page payload. It has no JavaScript bundle on the client. It cannot use state, effects, refs, or any browser API. It can be async, awaiting database calls or fetches directly inside the component body.
A Client Component runs in the browser, marked with the 'use client' directive at the top of the file. It can use hooks, state, effects, event handlers, browser APIs. Its source code is in the JavaScript bundle the browser downloads.
The two compose freely in one direction: Server Components can render Client Components as children. A Client Component cannot render a Server Component as its descendant unless the Server Component is passed in as a prop (the children slot is the canonical case). That asymmetry exists because by the time a Client Component runs, we are already in the browser and the server is gone.
What only Server Components can do
Four things move into the component itself once you stop being afraid of the server runtime:
The one I find most teams underuse is the database read. You do not need a REST endpoint for your own page's data when the component can query Postgres directly. The /api/users endpoint that exists only so the page can fetch from it is a layer you added back in 2018 because the component had no other way to get data. That layer is now optional.
No useEffect. No loading state. No race conditions. No /api/users route. The data is on the page when the page is on the screen. If the database call takes 200 ms, the response from the server takes 200 ms, and the user sees nothing during that time only because the server held the response. Streaming with Suspense lets you split that further, but the core simplification is real.
The second underused superpower: importing heavy code that never enters the bundle. A markdown library, an image-dimension parser, a syntax highlighter. In a Client Component, importing those costs every visiting browser the kilobytes. In a Server Component, the import cost is paid once, on the server, and the browser sees only the rendered output.
What only Client Components can do
The other side of the line, equally important:
A tabs component that highlights the active tab on click, a modal with focus management, a search box with debounced input, an animated chart, a drag-and-drop list. All of those need state, effects, or DOM access. None of those can live in a Server Component.
The rule is simple: if the component owns mutable interactive state, it is a Client Component. If it just shows data, it is a Server Component. Everything in the codebase falls into one of those buckets, and getting the bucket right matters.
The boundary: how a tree composes them
The pattern I keep reaching for in every Next.js app I work on:
The wrapper components, the data fetchers, the static layout pieces are Server Components. The interactive leaves are Client Components. The Client Components are small, focused, and the only places where bundle cost is paid.
This shape is the inversion of the old SPA pattern, where the entire page was a Client Component because that was the only option. Now most of the page is server-rendered HTML with small islands of interactivity, and the bundle shrinks accordingly.
The one part of this that confuses people first time: a Server Component cannot be imported into a Client Component, but a Client Component can take a Server Component as children. That means a <Modal> Client Component can wrap any server-rendered tree:
<ServerRenderedHeart /> is a Server Component passed into a Client Component via the children slot. That works. What does not work is import ServerHeart from './ServerHeart' inside the Client Component file: that would try to drag the server code into the client bundle.
Five patterns I reach for
Five shapes that come up over and over in real apps:
1. Server-rendered list, client-rendered row interactions. The list is a Server Component that fetches and maps. Each row's interactive bit (a like button, a delete button) is a Client Component. The bundle pays for the row interaction component once; the rendering of the list itself adds nothing.
2. Server-fetched data passed to a Client Component for filter / sort. The Server Component fetches the full dataset (or a sensible page) and passes it as a prop to a Client Component that owns the filter UI and the filter state. The first paint is fast; the filter work is local.
3. Form with server actions. The form shell is a Server Component. The submit handler is a server action ('use server'). The Client Component is only the input or the submit button if it needs useFormStatus for the pending UI. No more /api/submit endpoints just for forms.
4. Suspense boundaries for slow data. Wrap the slow Server Component in <Suspense fallback={...}>. The page streams the fast parts immediately and fills in the slow part when ready. This is the right tool for "the recommendations are slow but the rest of the page should not wait".
5. Client Component as a context provider, Server Components as consumers via children. The auth context, the theme context, the toast manager. They live in Client Components at the root, but the children inside them are still Server Components, because the context boundary is at the runtime boundary.
The traps that bite teams new to RSC
Four traps I have personally hit or watched a team hit:
Trap 1: every file gets 'use client' defensively. A team that does not understand the boundary marks every file 'use client' to be safe. The whole app becomes a client app again, none of the bundle savings materialize, none of the database-direct simplicity materializes. The fix is a one-time sweep: remove 'use client' from any file that does not actually use a hook or browser API, and let the build error tell you which ones genuinely needed it.
Trap 2: data fetching duplicated on both sides. The page fetches users on the server and renders them. The Client Component then re-fetches the same users on mount because nobody told the developer the data was already there. Pass the data through props. The whole point is that the server already did the work.
Trap 3: secrets leaking into the client. A Server Component reads process.env.STRIPE_SECRET_KEY and inlines it into the props of a Client Component. Now the secret is in the page payload, visible to anyone with view-source. The rule: never serialize a secret as a prop to a Client Component. Pass only the data the client genuinely needs.
Trap 4: trying to use useState in a Server Component. The error message is clear, but the underlying confusion is not always: the developer thought useState was a React thing, not a runtime thing. It is a runtime thing. State exists only where the component runs, which for a Server Component is the server, where there is no "between renders" concept. If the component needs state, it is a Client Component.
When the Server Components default does not apply
The Server Components default makes sense in a Next.js App Router or a Remix codebase. It does not make sense if you are building an SPA with Vite, no server runtime, no streaming. There, every component is a Client Component because that is the only option, and the architecture decisions are the older ones: code splitting, lazy routes, query libraries.
It also makes less sense for highly interactive apps that are mostly state and animation: a design tool, a spreadsheet, a video editor. The boundary is so clientward in those apps that a Server Components shell only wraps a single big Client Component, and the architectural simplification is small. Use the model that fits the app's interactivity profile.
For everything in between, which is most product UI on the web (dashboards, content sites, e-commerce, internal tools), the Server Components default is the right call. The bundle gets smaller, the data layer gets simpler, and the parts of the codebase that need to be interactive end up clearly identified instead of being indistinguishable from the parts that do not.
