Mixing without dragging
Rules of composition:
- A server component can render a client component. This is the normal direction: the page (server) sprinkles in interactive leaves (client).
- A client component cannot import a server component. Importing pulls it into the client bundle, where server code cannot run.
- But a client component can receive server-rendered content as
children. The classic props pattern from the React course is the escape hatch.
Pattern 3 is worth a picture in code:
// app/page.js (server) import Collapsible from "./collapsible"; // a "use client" wrapper import HeavyArticle from "./article"; // stays a server component export default function Page() { return ( <Collapsible> <HeavyArticle /> </Collapsible> ); }
Collapsible handles the open/close click in the browser, while HeavyArticle is rendered on the server and passed in as already-finished content.
Why Collapsible keeps HeavyArticle off the wire
The reason the pattern works is the order of operations. The server renders HeavyArticle first, then passes the finished result into the client component as children.
From Collapsible's point of view there is no article component at all, only a slot of already-rendered content that arrived as a prop. It never imports the article's code, so that code is never added to the client bundle and never travels to the browser. Collapsible ships only the few lines it needs to toggle a boolean and show or hide its slot.
This is the same "components as props" composition you used in the React course. Here it does double duty, keeping bundle size down as well as keeping code tidy.
What an import across the boundary costs
Now imagine a "use client" component adding import HeavyArticle from "./article" at the top of its file, where article.js has no directive of its own. HeavyArticle is pulled across the boundary and becomes client code, shipped to the browser.
The boundary is drawn by imports, not by each file's own directive. Importing a file from client code drags that file into the client bundle regardless of what the file itself says. Worse, if the imported file uses server-only APIs such as a database client or a secret environment variable, the build errors out, because those things genuinely cannot run in a browser.
That is exactly why rule 2 says a client component cannot import a server component. When you need server-rendered content inside a client component, pass it as children instead.
Choosing in practice
A quick decision table you can apply to any component:
| What the component does | Where it belongs |
|---|---|
Uses useState, useEffect, refs, or event handlers | client |
Uses browser APIs such as localStorage or window | client |
| Fetches data, reads env vars, or queries a database | server (the default, do nothing) |
| Just renders markup from props | server |
| Both data and interactivity | split it in two |
That last row is the common case in real code. Write a server component for the data and markup, then give it a small client child for the interactive part.
This mirrors the frontend/backend split you already lived in the React and Backend with Node.js courses, except the boundary now runs through one component tree instead of between two repos.
Counting client components in a blog page
Here is a realistic tree, with four components:
| Component | What it does | Verdict |
|---|---|---|
PostBody | Renders markdown from props | server |
CommentForm | Textarea driven by useState | client |
AuthorCard | Renders props | server |
ThemeToggle | onClick plus localStorage | client |
Two of the four need "use client": CommentForm, because it holds state, and ThemeToggle, because it handles a click and reaches for a browser API.
PostBody and AuthorCard only turn props into markup, so they stay server components and ship no JavaScript at all.
Why it works out that way
- Rendering from props needs no JavaScript in the browser. The HTML the server produced is already the final answer.
- State and browser APIs both require code that is still alive after the page loads, which is the definition of a client component.