Abid Ali

Frontend Development

React Server Components vs. Client Components in Next.js

By Abid Ali · Published · Updated

Server Components ship no client JavaScript. Client files opt in with use client. Here is how to choose, and what pulls a whole page onto the client.

Cover illustration: React Server Components vs. Client Components in Next.js
Cover illustration.

React server components vs client components is a boundary, not a moral choice. Server Components run on the server, can read files and secrets, and add no JavaScript for that component to the browser bundle. Client Components are the ones that need state, effects, or browser APIs. In Next.js you opt into the client with the use client directive at the top of the file. Everything else in the App Router is a Server Component by default. Those are next.js server components, even when the file never says so.

What belongs on the server

A page that reads content and renders HTML can stay on the server. A blog index that reads markdown, a case study that reads a typed record, a sitemap: none of that needs useState. The HTML arrives ready. The browser does not download the code that read the files.

This fits:

  • Data loading and secrets (tokens stay on the server).
  • Rendering articles, product copy, and other content that does not change until navigation.
  • Metadata exports. generateMetadata is server-only.

OG Scope's check is the same idea at the network layer. The server fetches the URL, parses the tags, and returns the extracted values. The browser never receives the raw HTML of the site being checked. A Server Component, or a route handler, is the right place for that work. A useEffect fetch would put the request on the visitor and would fail on cross-origin pages.

What needs the use client directive

Add the use client directive when the file uses:

  • useState, useReducer, or useEffect
  • event handlers that update local state
  • browser-only APIs (window, localStorage) in render

On this portfolio, the header does this. It listens to scroll, toggles a menu, and highlights the active link. The contact form does this. It holds errors and a submit status. Both files start with use client. The pages that render them do not.

Meme Tap's favourites, stored on the device, are client state. The catalogue of sounds can still be server-rendered. The button that writes to localStorage is the client island.

The client component boundary

The client component boundary is the file with use client and every module it imports. Those imports are client code too. This is the expensive mistake: a use client page that imports the header, the form, the card list, and the footer. The whole route's JavaScript ships, including components that never used state.

Push the directive down:

// app/contact/page.tsx  (Server Component, no directive)
import ContactForm from "@/components/ContactForm";

export default function ContactPage() {
  return (
    <section>
      <h1>Contact</h1>
      <ContactForm />
    </section>
  );
}

ContactForm is the client file. The heading stays on the server. A client component boundary should be as small as the interaction.

You cannot import a Server Component into a client file. If a client shell needs server-rendered content inside it, pass that content as children from a server parent. The child is rendered on the server and handed through. The client file never imports it.

Mistakes

  • Adding use client to a layout "just in case." The layout wraps every page. Its imports become client code for the whole site.
  • Fetching public content in useEffect that the server could have rendered. The page flashes empty, and the data request waits for JavaScript.
  • Passing functions or class instances from a Server Component into a Client Component. Props must be serializable: plain objects, arrays, strings, numbers.
  • Reading window during render in a client component without a guard. The server pre-render does not have window. Put that read in an effect, or render a fallback until mount.

A quick audit

Search the repo for use client. For each file, name the state or browser API that required it. If you cannot name one, delete the directive. Then check what that file imports. If it imports a large presentational tree, split the interactive leaf out and leave the tree on the server.

That is the practical difference in react server components vs client components: server by default, client at the leaf that actually clicks, types, or listens.

Questions

Does use client mean the component is only rendered in the browser?
No. A client component is still pre-rendered on the server. The directive means its JavaScript is sent to the browser so state and effects can run there.
Can a Server Component import a Client Component?
Yes. That is the normal direction. A page can be a Server Component and render a client form or a client header as a child.
Can a Client Component import a Server Component?
Not as a direct import. Pass the Server Component as children from a Server Component parent, or keep the interactive piece small and leave the rest on the server.

Next in the reading path: A Practical Guide to Responsive Design Breakpoints

Let's discuss your next project.

Tell me what you are building, who it is for and what a good result looks like. I'll reply with questions, a suggested approach and the next steps.