In a React app that uses Server Components together with server-side rendering, where does a Client Component actually execute?
answer
- twice, not once
- the initial HTML comes from somewhere
- the server pass has no window
- hydration is what makes it interactive
- the server side never runs in the browser
basics
~20 sClient Components usually run twice: once on the server during server rendering to produce the initial HTML, then in the browser, where they hydrate and handle every later update. 'Client' means shipped and interactive, not skipped on the server.
solid answer
~50 sIn both places, and that surprises people. During the server render React also executes Client Components to produce the initial HTML, so the user sees content before any JavaScript loads. Their code is then shipped to the browser, where React runs them again to hydrate the markup and re-renders them on every state or prop change from then on. The label describes what happens *in addition* to the server pass, not instead of it. The practical consequence is that a Client Component's module scope and render body must be able to run with no DOM: touching `window`, `document` or `localStorage` there crashes the server render. Read browser-only values in an effect or a ref callback, which run only after mount in the browser. A Server Component, by contrast, never runs in the browser at all — that asymmetry is the real distinction.
code
jsx · 15 lines'use client';
import { useState, useEffect } from 'react';
export default function ViewportWidth() {
const [width, setWidth] = useState(null);
useEffect(() => {
const update = () => setWidth(window.innerWidth);
update();
window.addEventListener('resize', update);
return () => window.removeEventListener('resize', update);
}, []);
return <span>{width === null ? 'measuring' : `${width}px`}</span>;
}go deeper
Remember that a Client Component is rendered on the server for the initial HTML and again in the browser — 'client' means its code ships, not that the server skips it.
Explain the two passes and what follows: module scope and render bodies must run without a DOM, so browser-only reads belong in an effect or a ref callback.
Use this when diagnosing — a server-render crash on a browser global, or a component behaving differently before and after the bundle loads, points straight at code running in the pass you forgot about.
Frame the separation for the team: Server Components decide what code ships, server rendering decides what the first response contains, and conflating the two is what produces most of the confusion in reviews.
## The naming trap "Client Component" reads like "component that runs on the client", and candidates infer "therefore not on the server". That inference is wrong, and it is one of the most common corrections an interviewer will fish for. The accurate reading is: a Client Component is a component whose code is **shipped to the browser and stays alive there**. In a server-rendered application, it is also executed on the server first, to produce the HTML the user sees before any JavaScript has downloaded. ## The two passes On a first page load, an RSC application with server rendering does roughly this: 1. **Server Components render** on the server. Their code stays there; their output is a description of UI, which includes placeholders for the Client Components they rendered and the props those components receive. 2. **Client Components are rendered on the server too**, to turn that description into HTML. This pass runs your Client Component functions in a JavaScript runtime with no DOM. 3. The browser receives HTML and paints it. Nothing is interactive yet. 4. The client bundle arrives, and React runs the Client Components again in the browser, attaching them to the existing markup so they become interactive. 5. From then on, every state change re-renders Client Components in the browser only. Server Components never run again on the client. So the count for a first load is: Server Component — once, on the server. Client Component — once on the server, once in the browser, and then as many times as its state and props require. ## What this breaks in practice Because step 2 has no DOM, anything a Client Component evaluates at module scope or during render must be DOM-free: ```jsx 'use client'; // Crashes the server render: no `window` there. const initialWidth = window.innerWidth; ``` The same applies to a read in the render body, and to `document`, `localStorage`, `navigator`, and anything a third-party library touches at import time. The fix is to move the read to a phase that only happens in the browser — an effect, or a ref callback — and hold the result in state: ```jsx 'use client'; import { useState, useEffect } from 'react'; export default function Width() { const [width, setWidth] = useState(null); useEffect(() => setWidth(window.innerWidth), []); return <span>{width ?? 'unknown'}</span>; } ``` Render bodies must be able to run in an environment with no browser at all. That is a real constraint on how you write Client Components, and it is the practical reason knowing where they execute matters. ## Why the server pass exists at all If Client Components are going to run in the browser anyway, why render them on the server? For the same reason server-side rendering has always existed: meaningful HTML in the first response. The user sees content while the bundle is still downloading, and crawlers and link previews get real markup rather than an empty root element. That is also why the two ideas are separable. Server Components are about *what code ships*. Server-side rendering is about *what the first response contains*. RSC applications typically use both, but a Client Component's double execution comes from the second, not the first. ## The asymmetry to state clearly - Server Component: runs on the server. Never in the browser. Not on first load, not after hydration, not on any update. - Client Component: runs on the server for the initial HTML, then in the browser for hydration and every update afterwards. If you can only remember one line, remember that one — it resolves most of the confusion around this model, and it explains both the `window is not defined` crash and the fact that a page full of Client Components still arrives with real content in it. ## A quick way to check yourself A `console.log` at the top of a component body is a decent diagnostic. If it appears only in the server process's logs, you are looking at a Server Component. If it appears in the server logs *and* the browser console, it is a Client Component doing both passes. If it appears only in the browser, the component is being rendered client-side after load — from an interaction or a client-only route, not from the initial HTML pass.
- A Client Component reads `window.innerWidth` during render and the server render crashes with 'window is not defined'. What is the fix?Move the read out of the render path: take it in an effect and keep the result in state, so it only ever runs in the browser after mount. Component bodies and module scope must be executable in an environment with no DOM, because the initial HTML is produced there.
- Does a Server Component ever run in the browser?Never. That asymmetry is the whole point of the split: Client Components run in both places, Server Components only on the server. If you can find a component's source in the browser's loaded scripts, it was part of the client bundle.
- How would you quickly determine which bucket a component is in while debugging?Put a log at the top of its body and see where it appears. Server-process logs only means Server Component; both the server logs and the browser console means a Client Component doing its two passes. It is faster than tracing imports back to a directive.
saying these in an interview costs you the question
- Says Client Components never execute on the server
- Reads window at module scope and blames the framework
- Thinks 'client' means the HTML arrives empty
- Believes Server Components run again after hydration
- Confuses shipping code with rendering on the client only