← React: components, state and interface decisions
10 / 10 · 65 MIN

Delivery, hydration and server boundaries

Distinguish client mounting from hydration, preserve initial output and interpret module boundaries in React Server Components. Bound transported data and raw HTML.

Client mounting or hydration

The decision starts with the received container. If a client application starts in an empty node without pre-rendered React HTML, createRoot creates the root and root.render displays the tree. If the container already contains HTML produced by React on the server, hydrateRoot attaches the tree to existing content.

The API need not be called again for each state change. Hydration assumes the first client output matches server output. Treating a mounting API as an interchangeable substitute can recreate content intended to be retained.

In a framework, startup may already belong to its infrastructure; do not add another root without understanding that contract.

Preserve the first snapshot

In the example, initialPrice is a display string retained by the host from server rendering. PriceReview starts with that value and changes only after an action. If the client replaces it with another price before hydration, it produces different initial output.

Separately computed dates, newer data or conditional branches based on window can also create differences. Correction depends on the cause: retaining the initial snapshot and scheduling a later update is one strategy when requirements allow it.

The example does not provide the server or safe encoding of data in an HTML document; those contracts belong to the host.

Recovery does not replace reconciliation

React recovers from some hydration errors, but the API does not guarantee fixing every attribute difference. onRecoverableError exposes recovered errors and component-tree information. Its destination should match the application’s observability policy. suppressHydrationWarning is a one-level escape hatch for an unavoidable, bounded difference; it is not recursive repair and does not patch mismatched text.

Calling root.render before hydration finishes is not a harmless shortcut either: it can clear existing HTML and switch the entire root to client rendering. First reconcile output and startup order.

Server Components and module dependencies

React Server Components use a separate evaluation environment that can run during build or per request. The use client directive marks a boundary in the module dependency tree: the module and its transitive imports belong to the client subtree.

Visual position alone cannot determine evaluation environment. A Server Component can create JSX and pass it as children to a Client Component that does not import that module. Reading only JSX hierarchy can therefore produce a wrong diagnosis.

The directive belongs at the file’s beginning, before imports. This lesson interprets the React contract; it does not present a complete framework routing or bundling implementation.

Transport supported data

Props crossing from Server to Client Components need values supported by React serialization. A plain object with strings, numbers and booleans can represent an invoice without transporting a domain-class instance and its methods. The supported list is not limited to JSON: it also includes documented types such as Date, Map, Set, JSX elements and Server Functions.

That does not authorize transporting any local closure or arbitrary class. If interaction needs only id, total and approved, a plain object makes the contract explicit and local logic can be defined in the client module. The module boundary does not grant business permissions.

Raw HTML and content trust

dangerouslySetInnerHTML replaces content with raw HTML and requires a trust decision. A string stored in a database can still contain third-party input. Neither key, Suspense nor origin in a Server Component sanitizes that string.

If the interface only needs text, use the text path. If it needs HTML, retain an explicit trusted, sanitized-content policy and apply it before that boundary. Even when Trusted Types is used, the policy creating TrustedHTML remains responsible for accepted data.

The aim is to identify where content stops being only data and is interpreted as HTML.

Delivery acceptance and version limits

At acceptance, record whether the product uses client mounting, server HTML with hydration or an RSC integration. Check the initial data contract and observe later interactions without confusing Mark for refresh with a real request: in the example it only changes local text.

Custom bundler implementations need another distinction: Server Components are stable in React 19, but underlying bundler and framework APIs can change between minor versions. Documentation recommends pinning a specific version in that context.

An update must respect supported integration, transported data and initial correspondence; visible HTML does not demonstrate all those contracts.

// Shared component: the server must render this same initial output.
import {useState} from 'react';
import {hydrateRoot} from 'react-dom/client';

export function PriceReview({initialPrice}) {
  const [price, setPrice] = useState(initialPrice);
  return <section>
    <h2>Price review</h2>
    <p>{price}</p>
    <button type="button" onClick={() => setPrice('Pending refresh')}>
      Mark for refresh
    </button>
  </section>;
}

// Browser bootstrap called by the host, after its DOM is available.
// container contains server React HTML for the same PriceReview/initialPrice.
// initialPrice is the same display string used on the server.
export function attachPriceReview(container, initialPrice, reportRecovery) {
  return hydrateRoot(container, <PriceReview initialPrice={initialPrice} />, {
    onRecoverableError(error, info) {
      reportRecovery(error, info);
    }
  });
}
IN PRACTICE

The host supplies the container, the same price string used on the server and an observability callback. Initial rendering preserves that string; the later click only changes local text to Pending refresh. No refresh request, HTML data serialization or RSC framework is implemented in this example.

Common pitfalls

Using hydrateRoot on an empty container; computing different data before initial render; treating suppressHydrationWarning as recursive repair; confusing visual children with imports; transporting arbitrary classes; trusting HTML merely because it came from a database.

Related topics: Components, props and pure rendering · State structure and component identity · Testing behavior and evidence boundaries

Take this idea with you

Hydration attaches behavior to initial output that must match. Server/client boundaries belong to the module and data contract; they do not remove the need for authorization or explicit content trust. Use the compatible framework and supported version without inferring stability of underlying APIs.

Create account

Reference: Initial hydration contract · React 19.3.0; JSX