Server Components and Suspense
React's rendering model now spans two runtimes: components that run only on the server and components that ship to the browser, stitched together by Suspense boundaries and streamed HTML. Interviewers press on this because it changes where data is fetched, what lands in the bundle, and why hydration errors appear at all.
part ofReactoverview, primer and where to startread it →on this pageshowhide
explore
- React Server Components14 questions
- Server vs Client Components5 questions
- 'use client' and the Boundary4 questions
- Serializable Props Across the Boundary5 questions
- SSR and Hydration15 questions
- Server Rendering APIs5 questions
- Hydration5 questions
- Hydration Mismatches5 questions
- Suspense Boundaries13 questions
- Fallback Semantics and Placement4 questions
- Suspense for Data4 questions
- Streaming with Suspense5 questions
- Actions and Form Primitives5 questions
questions
page 2 of 2Suspense data reading is usually described as requiring a cache keyed by request identity. Beyond memoizing a resolved value, what must such a cache guarantee for use() to behave correctly in React 19?
basics
~20 sIt must return the identical promise for a given key on every render, dedupe requests already in flight, remember rejections as well as results, and be scoped per request on the server so data cannot leak between users.
A React page uses streaming server rendering and Suspense boundaries deep in the tree, yet time-to-first-byte is still ~900 ms because a top-level component above every boundary awaits a slow API before rendering. Explain why the deeper boundaries do not help, and what actually fixes it.
basics
~20 sNothing can be flushed until the shell is renderable, and the shell is everything outside pending Suspense boundaries — including that top-level await. Boundaries below it are irrelevant. The fix is to move the slow work inside a boundary, or start the request early and pass the unawaited promise down to a suspended child.
On a streamed React 19 page, three sibling <Suspense> boundaries finish loading in a different order than they appear in the document. In what order does their content appear to the user, and how would you make a group of boundaries reveal top-to-bottom instead?
basics
~20 sSibling boundaries reveal in completion order, not document order, so a later section can pop in first. Stable React 19 has no reveal-order coordinator, so you enforce order structurally: nest boundaries so an outer one must reveal before its children, or group the sections behind a single boundary that reveals them together.
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?
basics
~20 sPromises 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.
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.
basics
~20 sThe 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.
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.
basics
~20 sThe 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.
A server-rendered React app chooses its theme by reading localStorage during render. In the browser it briefly shows the light theme and logs a hydration mismatch. Explain the cause and the options you would consider for fixing it.
basics
~20 slocalStorage does not exist on the server, so the server rendered a default theme while the browser's first render read the stored one — two different trees. Fix it by rendering a server-known default and applying the stored theme after hydration, or by setting it before hydration outside React's tree.
What is the onRecoverableError option of React's hydrateRoot and createRoot, when does React call it, and what does "recoverable" mean for the page the user is looking at?
basics
~20 sonRecoverableError is an option of hydrateRoot and createRoot that React calls when it hit an error and recovered by itself — typically a hydration error it fixed by re-rendering that content on the client. The UI is correct, so wire it to your error reporter.
On a server-rendered React 19 page, a user clicks a button before hydration has finished. What happens to that click, and what does React do about interactions that arrive during hydration?
basics
~20 sIf hydrateRoot has already started, React captures the click on the root container, prioritizes hydrating the subtree the user touched, and replays the event once that subtree is ready. If the bundle has not executed yet, no listener exists and the click is lost.
When streaming a React app with react-dom/server's renderToPipeableStream, what is the difference between the onShellReady and onAllReady callbacks, and how does that choice affect the HTTP status code you can send?
basics
~20 sonShellReady fires when everything outside Suspense boundaries has rendered; piping there streams and locks the status code immediately. onAllReady fires only after every boundary has resolved; piping there gives up streaming but keeps the status changeable until the whole page is known good.
React 19 ships use() and <Suspense> but no client-side data cache of its own. Which responsibilities does React deliberately leave to a framework or data library, and how would you decide who owns them in an app you are leading?
basics
~20 sReact defines only the render-time protocol: suspend on a pending promise, retry when it settles, surface rejection to an error boundary. Caching, deduping, invalidation, revalidation, retries and server-to-client data transfer are policy, and a framework or library supplies them.
How do you decide where to place <Suspense> boundaries across a React page, and what goes wrong at each extreme — one boundary around everything versus a boundary around every component?
basics
~20 sPlace a boundary where a placeholder can replace a region without moving anything around it, and where that region's data genuinely arrives at a different time from its neighbours. One boundary makes the whole page wait on its slowest part; a boundary per component produces a flickering mosaic that reflows repeatedly.
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?
basics
~20 sTreat 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.
A server-rendered React page paints in under a second but stays unresponsive for several seconds afterwards while hydration runs. As the engineer who owns this page, how do you reason about hydration cost, and what tradeoffs do you weigh in reducing it?
basics
~20 sHydration cost scales with the size of the client component tree, not with what the user can see. Reduce it by keeping non-interactive parts out of that tree and splitting what remains so interactive regions hydrate first, then verify with real-user interaction data.
Progressive enhancement is cited as a benefit of React 19's form Actions. On a server-rendered page that has not yet hydrated, what can a user actually do with such a form, and which parts of the Action experience only start working once hydration finishes?
basics
~20 sBefore hydration the browser can still submit the form the ordinary HTML way, so the mutation happens provided the action resolves to a real server endpoint. Everything React layers on — pending UI, optimistic updates, staying on the page — requires hydration.
A React Server Components render produces a serialized RSC payload in addition to HTML, and that payload is streamed too. How does a <Suspense> boundary shape it, and why does that matter for a client-side navigation where no new HTML document is fetched?
basics
~20 sThe RSC payload is a stream of serialized rows describing the rendered server tree. A suspended subtree is emitted as a placeholder reference immediately, with its resolved rows arriving later in the same stream. On a client navigation the browser fetches that payload instead of HTML, so boundaries still let part of the new screen render before the slow parts arrive.
A React server render has already streamed its shell, but one Suspense boundary is still waiting on data five seconds later. How do you cut the render short with react-dom/server's streaming APIs, and what does the browser end up with?
basics
~20 sSet a timer and call the abort function that renderToPipeableStream returns, or abort the AbortController whose signal you passed to renderToReadableStream. React stops server work, sends the unresolved boundaries' fallbacks, closes the stream, and the client renders those regions itself.
showing 31–47 of 47