skip to content

React Server Components

The RSC model splits your tree into components that execute only on the server and components that also execute in the browser, with an explicit directive marking the seam. Expect questions on where the boundary goes, what can cross it, and what that buys you in bundle size.

part ofReactoverview, primer and where to startread it →
on this pageshow

explore

questions

14

In React 19, props passed from a Server Component to a Client Component are serialized into the RSC payload before they reach the browser. Which kinds of values survive that trip, and which ones make React throw?

level: juniorimportance: must knowfreq 68%

answer

  1. the boundary is a network hop
  2. data crosses, behaviour does not
  3. plain objects only, no prototypes
  4. Date and Map survive, it is not JSON
  5. functions throw unless 'use server'

basics

~20 s

Primitives, plain objects, arrays, Date, Map, Set, typed arrays, JSX elements, promises and Server Functions can cross. Ordinary functions, class instances and locally created symbols throw, because React must write every prop into a wire payload the browser reads back.

solid answer

~50 s

The boundary behaves like a network hop: React serializes every prop into the RSC payload the browser downloads, so a prop has to be expressible in that format. What crosses is data — strings, numbers, booleans, `bigint`, `null`, `undefined`, plain objects and arrays of serializable values, `Date`, `Map`, `Set`, typed arrays and `ArrayBuffer`, `FormData`, JSX elements, promises, and functions that are Server Functions marked with `'use server'`. What throws is behaviour and identity — ordinary functions and closures, class instances or anything whose prototype is not one of those built-ins, and symbols not registered via `Symbol.for`. The check is recursive, so a function nested three levels inside an object prop fails too. Note the format is richer than JSON: a `Date` arrives as a real `Date` and a `Map` as a `Map`. The working rule is: pass data down, keep behaviour inside the client module.

code

jsx · 15 lines
jsx
// server.jsx — a Server Component
import ClientPanel from './ClientPanel';

export default function Panel() {
  return (
    <ClientPanel
      // crosses: primitives, plain object, Date, Set, JSX
      title="Sales"
      total={42n}
      meta={{ region: 'eu', updatedAt: new Date() }}
      tags={new Set(['live'])}
      footer={<p>generated on the server</p>}
    />
  );
}

go deeper

for a junior

Be able to state the allowed list and the forbidden list without hesitating, and give the reason in one sentence: the props are serialized and sent to the browser. Saying "pass data, not functions" already earns the point.

for a middle

Explain why the format accepts a Date and a Map but rejects a class instance — plain objects have nothing but data, a class instance is defined by a prototype that cannot travel. Note that the check is recursive.

for a senior

Show that you treat the boundary as an API contract: map domain objects to explicit plain shapes on the server, and expect the error at server render time with a prop path you can read. Mention that Server Function arguments obey the same rules.

for a principal

Own the standard for what the boundary is allowed to carry: a defined view-model per boundary, one mapping place per entity, and awareness that whatever crosses is both downloadable by the user and a shape you now have to keep stable.

## Why there is a wire format at all A Server Component runs once, on the server, and never ships its code to the browser. Its output is not HTML — it is a serialized description of the UI, the RSC payload (React's "Flight" format), which the browser downloads and React turns back into elements. In that description a Client Component is not executed; it appears as a placeholder that names the client chunk to load plus **the list of props to hand it**. That is the whole rule in one sentence: props crossing from server to client are written into a stream in one process and read back in another. They are data on a wire, not references in a shared heap. Everything else about serializable props follows from it. ## What may cross React documents the serializable set explicitly: - Primitives: `string`, `number`, `bigint`, `boolean`, `undefined`, `null`, and symbols **registered globally** with `Symbol.for('x')`. - Plain objects — those created from an object initializer or spread — whose properties are themselves serializable. - Iterables of serializable values: arrays, `Map`, `Set`, `TypedArray`, `ArrayBuffer`. - `Date`. - `FormData`. - JSX elements (server or client component elements) — this is what makes passing rendered `children` into a client component work. - Promises. - Functions that are Server Functions (exported from a `'use server'` module, or defined with an inline `'use server'` directive). ```jsx // Server Component export default async function Page() { const rows = await db.query('select id, name, created_at from users'); return ( <UserTable rows={rows.map((r) => ({ id: r.id, name: r.name, createdAt: new Date(r.created_at) }))} tags={new Set(['active', 'beta'])} /> ); } ``` ## What throws - **Ordinary functions**, including arrow functions defined inline in the server component's JSX. A function is code plus a closure over server-side scope; neither can be written to a stream. - **Class instances**, and more generally any object whose prototype is not `Object.prototype` or one of the supported built-ins — an ORM model, a `Response`, a `WeakMap`, your own `class Money`. Objects with a `null` prototype are rejected too. - **Symbols not in the global registry**, e.g. `Symbol('rowId')`, because there is no way to recreate the same symbol on the other side. The error surfaces during the server render, and React names the offending prop path in the message — text along the lines of *only plain objects can be passed to Client Components*, or for a function, that functions cannot be passed unless marked `'use server'`. ## Richer than JSON, poorer than a JS heap Candidates often assume the boundary is `JSON.stringify`. It is not, and both directions of that difference matter. Richer: `undefined` survives as `undefined` rather than being dropped; `bigint` survives; a `Date` arrives as a live `Date` you can call `.getTime()` on; a `Map` arrives as a `Map`. You do not need to hand-encode those types. Poorer: nothing that carries *behaviour* survives. There is no way to send a method, a getter's identity, a live subscription, or an object that is meaningful only because of its class. That is not a limitation React could lift — the client process has no such object to reconstruct. ## Direction matters The rule applies to props flowing **server → client**. Two related cases obey the same serialization set: the arguments a client component passes when it calls a Server Function, and that function's return value, both travel over the network with the same constraints. Props between two client components are ordinary JavaScript — functions, class instances and anything else are fine there, because no boundary is crossed. ## The practical consequence Design the boundary as *data in, behaviour local*. If a client component needs to do something, the function that does it lives in the client module (or is a Server Function it calls). If a server component has a rich domain object, it maps it to a plain shape before passing it. Thinking of the props as an API response, rather than as arguments to a function call, gets almost every case right on the first try.

  • Does the same restriction apply when a Client Component calls a Server Function with arguments?
    Yes. The arguments are serialized and sent to the server, and the return value is serialized coming back, using the same set of allowed types. So you can pass an id, a plain object or `FormData`, but not a callback or a class instance in either direction.
  • A prop is a plain object, but one of its nested properties is a function. Does that pass?
    No. Serialization is recursive, so React walks the whole prop value and fails on the nested function just as it would on a top-level one. The error message points at the property path, which is usually enough to find it.
  • Why can you pass JSX as a prop when a component is itself a function?
    You are not passing the component function — you are passing an element, which is a plain description: a type reference, props and children. For a server-rendered element the server has already rendered it, so what crosses is its output, not the code that produced it.

saying these in an interview costs you the question

  • Thinks props are passed by reference like any parent to child
  • Says the payload is JSON, so Dates arrive as strings
  • Claims wrapping a function in an object gets it across
  • Believes class instances work because they are just objects
  • Applies the restriction to client-to-client props as well

context

open as a page

In a React 19 app that uses React Server Components, what is the fundamental difference between a Server Component and a Client Component?

level: juniorimportance: must knowfreq 78%

basics

~20 s

Server Components execute only on the server, once per request, and their code never ships to the browser. Client Components are sent to the browser as JavaScript, where they mount, hold state, and respond to user input.

open as a page

In a React Server Components app, a file that starts with 'use client' imports a Button from another file that has no directive. Is that Button a client component, does its own file need a 'use client' line, and where in a file must the directive be written?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Yes, the Button is a client component. Files below a client boundary inherit it through imports and need no directive of their own. The directive must be the first statement of a file, above every import.

open as a page

A React Server Component renders `<SaveButton onSave={() => saveRow(id)} />`, where SaveButton is a Client Component, and the render fails with an error about functions. Why does React reject that prop, and what are the legitimate ways to give that button behaviour?

level: middleimportance: must knowfreq 72%

basics

~20 s

A prop crossing into a Client Component is serialized and sent to the browser, and a function is code plus closure over server scope, so it cannot travel. Define the handler in the client module, or pass a Server Function marked 'use server'.

open as a page

Why can a React Server Component not call useState or useEffect, or attach an onClick handler to the DOM elements it renders?

level: middleimportance: must knowfreq 68%

basics

~20 s

A Server Component runs once on the server and then no longer exists, so nothing in the browser can re-render it, commit an effect, or run a handler for it. React rejects those hooks and tells you to add 'use client'.

open as a page

In a React Server Components app, an interactive collapsible panel is a client component and needs to display a server component that queries the database. Why can't the panel import that server component, and what composition pattern lets the server-rendered content appear inside it?

level: middleimportance: must knowfreq 66%

basics

~20 s

Importing it would pull the server component into the client bundle, so it could no longer run on the server. Instead a server parent renders it and passes it into the panel as children or a slot prop — already rendered, so it crosses as output, not as code.

open as a page

In a React app using Server Components, what does the 'use client' directive at the top of a module actually do to the module graph, and why is it described as a boundary rather than a per-component switch?

level: middleimportance: must knowfreq 75%

basics

~20 s

'use client' marks a module as an entry point into the client bundle: that file and everything it imports become client code. It opens a boundary in the module graph rather than switching one component into client mode.

open as a page

In a React Server Component you fetch a record with an ORM and pass the returned model instance straight into a Client Component as a prop, and React throws that only plain objects can be passed. Why does a Date prop work but the model instance not, and how do you fix it?

level: middleimportance: should knowfreq 48%

basics

~20 s

The RSC payload has built-in encodings for a fixed set of types including Date, but any other object must be a plain object built from an initializer. A class instance is defined by its prototype and methods, which cannot travel, so map it to a plain shape first.

open as a page

A React Server Component can be declared as an `async function` and `await` data directly in its body. What does React do with that, and why can a Client Component not be written the same way?

level: middleimportance: should knowfreq 55%

basics

~20 s

React awaits the promise as part of rendering that subtree and renders the component once the data arrives, so no loading flag or effect is needed. On the client, React 19 does not support async function components.

open as a page

In a React app that uses Server Components together with server-side rendering, where does a Client Component actually execute?

level: middleimportance: should knowfreq 58%

basics

~20 s

Client 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.

open as a page

In React 19, a Server Component passes an un-awaited promise as a prop to a Client Component instead of awaiting it first. What does React do with that promise, and what does the client side have to get right?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Promises are serializable across the RSC boundary. React sends the prop as a pending reference and streams the resolved value as a later chunk of the same response, so the server does not block. The resolved value must itself be serializable, and rejection needs an error boundary.

open as a page

You are reviewing a React 19 app that uses Server Components. For each of these, say whether it belongs in a Server Component or a Client Component and why: (1) calling an internal API with a key read from an environment variable; (2) formatting values with a 180 kB internationalisation library into static text; (3) a modal whose open/closed state toggles on a button press; (4) re-sorting a 5,000-row table when the user clicks a column header.

level: seniorimportance: should knowfreq 48%

basics

~20 s

The secret-bearing call and the heavy formatting belong in Server Components, because that code never ships to the browser. The modal toggle and the click-driven table sort need state and event handlers, so they must be Client Components.

open as a page

A team puts 'use client' at the top of a shared components/index.js barrel that re-exports their whole component library, and the client JavaScript bundle grows sharply. Explain the mechanism, and how you would restructure the boundary.

level: seniorimportance: should knowfreq 45%

basics

~20 s

The directive makes the barrel a client entry point, so every module it re-exports — and everything those import — becomes client code. The fix is to remove it from the barrel and mark only the individual modules that genuinely need interactivity.

open as a page

In a large React codebase using Server Components, everything passed as a prop into a Client Component is serialized into a payload the browser downloads and can read. How do you set the standard for what is allowed to cross that boundary?

level: principalimportance: should knowfreq 28%

basics

~20 s

Treat the server-to-client prop boundary as a published API, not an internal call. Define an explicit view model per boundary, map to it in one auditable place per entity, and review it for three things: what the user can read, how many bytes it costs, and how stable the shape is.

open as a page