Skip to content

API reference > @kontsedal/olas-react > HydrationBoundary

HydrationBoundary() function ​

Hydration boundary for SSR: constructs a Root<Api> once on the client with the supplied DehydratedState (typically serialized into the HTML by root.dehydrate() on the server), then provides it to descendants.

Usage:

tsx
// server: render -> root.dehydrate() -> serialize into HTML. Query data
// is untrusted text: `serializeForScript` escapes it for the script, so a
// `</script>` inside it cannot end the tag.
import { serializeForScript } from '@kontsedal/olas-core'
const html = `<script>window.__OLAS_STATE__ = ${serializeForScript(root.dehydrate())}</script>`

// client entry: `hydrate` needs a query engine to land in.
<HydrationBoundary
  def={appController}
  options={{ deps, queries: queryEngine(), hydrate: window.__OLAS_STATE__ }}
>
  <App />
</HydrationBoundary>

The boundary **owns** the root: it is built during the first render, so the children can read hydrated data in that render. options is read **once** on mount — a new inline options={{...}} on a parent re-render is intentionally ignored (so the example above doesn't discard cache state every render). The root is recreated only when the def identity changes; to swap it on navigation, pass a different def (or re-key the component).

**Streaming.** The batches createStreamingHydrator wrote into the page before the boundary built its root go into that root's hydrate, so the hydrating render reads them and no fetch starts for them. Later batches arrive through the intake. A root built for a new def takes none of them: the stream described the first root's tree.

**Unmount, and a hidden <Activity>.** React cleans up the boundary's effects on an unmount and when an <Activity> above hides it, and gives no way to tell the two apart. So a cleanup suspends the root, and disposes it a minute later unless the boundary's effects run again first, as they do when the <Activity> shows. A hide shorter than that keeps the root and its state; after a longer one the boundary builds a fresh root from options.

**A render that never commits.** A child that suspends or throws before the boundary's first commit makes React discard the render, and the root with it. A retry of the same element reuses that root. A root no commit claims is disposed about ten seconds after its work goes idle, or after a minute if it never goes idle. A <Suspense> above that later hides the boundary's content does not dispose the root: a hide is not an unmount. A parent below an outer<Suspense> re-creates the element on every retry; that retry reuses an unclaimed root built from the same def, the same hydrate object anddeps with the same members. When those options change between attempts, the retry builds a new root and refetches, and a development build warns once. A <Suspense> inside HydrationBoundary, around the part that suspends, avoids all of this: the boundary commits first.

**SSR contract.** During server rendering, callers construct a per-request root and pass it to <OlasProvider root={...} />, then dispose it after the response. The HydrationBoundary shape is the *client-side* mirror — it accepts a controller def + the dehydrated state and produces a root that matches what the server rendered. Rendered on the server, it builds a root that nothing disposes, because a server render runs no effects; a development build warns once when that happens.

Signature:

typescript
export declare function HydrationBoundary<Api>(props: HydrationBoundaryProps<Api>): ReactNode;

Parameters ​

Parameter

Type

Description

props

HydrationBoundaryProps<Api>

Returns:

ReactNode

Released under the MIT License.