MUI Docs Infra

Warning

This is an internal project, and is not intended for public use. No support or stability guarantees are provided.

Coordinated Lazy

CoordinatedLazy shows a loading placeholder until its content is ready, then swaps to the content — coordinated with every other block on the page so they settle as a single update instead of a staggered cascade. createCoordinatedLazy builds a self-loading component on top of it (a demo, a chart, a code frame): one deferred piece that loads its own data and swaps itself in. To stream a list of these in on the client, use useStream; to inject build-time data, use abstractCreateStream. It is the base of the Coordinated Streaming pattern.

What it does

The server pre-renders both the placeholder and the content; the client decides which to show and swaps when ready flips:

  • Force-mount-once — the placeholder mounts for one commit even when the content is already precomputed, so its hoist hook runs (the trick the code highlighter uses to recover its decompression dictionary).
  • Hoist up, content-context down — the placeholder hoists data with useCoordinatedFallback; the swap collects it and hands it to the content via useCoordinatedContent, so the full content reuses what the placeholder already fetched.
  • Nested suppression — an inner instance rendered inside an outer one's still-showing fallback stays in its own fallback, collapsing a "fallback → content → fallback → content" flicker into one transition.
  • Defer — hold the swap while real work is still in flight even though ready is true.
  • Settle registration — each swap registers with the page-global gate (and, when present, the ambient gate from a surrounding useStream controller), so a page-wide change coordinated with useCoordinated can wait for the initial swaps to land.

Basic Usage

Drive the swap yourself with the CoordinatedLazy component when you already have the data and a ready flag:

'use client';
import { CoordinatedLazy } from '@mui/internal-docs-infra/CoordinatedLazy';

function Block({ ready, data }) {
  return <CoordinatedLazy ready={ready} fallback={<Loading />} content={<Content data={data} />} />;
}

A code block frame is the simplest instance of this swap: its plain-text fallback → highlighted HAST is exactly a loading→full transition.

createCoordinatedLazy

createCoordinatedLazy(config) builds a component that loads one piece's data and swaps from ChunkLoading to ChunkContent for you — no manual ready wiring. The user's generic props T flow through to both; data (type P) is the loaded value (or the initial value while loading).

const ChartChunk = createCoordinatedLazy({
  ChunkContent,
  ChunkLoading,
  source: { mode: 'data', load: async (options) => fetchPoints(options) },
});

The returned component is isomorphic: each render it routes by how the data loads, so one component covers build, server, and client loading and server or client rendering.

SituationWhat rendersResult
preloaded/controlled data (build precompute)ChunkContent directlycontent is in the server HTML — no swap
a server Loader/InitialLoader, or a config sourceChunkServerLoader under Suspensethe loader runs + renders on the server, streams in (needs an RSC render context)
no config loader + a ChunkProvider (or forceClient)the 'use client' swapplaceholder on the server, then loads + swaps on the client from the ChunkProvider

So ChunkContent may be a server component when the data is preloaded or comes from a server loader, or a client component using hooks on the client-loading path — and the server-component routing is automatic, not a manual composition.

The data source (discriminated)

source is a discriminated union on mode, so each loading strategy is strongly typed — no overloads, no runtime return-sniffing:

modeLoads
dataone value directly (optionally a quick initial first)
urlsa list of chunk URLs, then each URL's data
streampushes chunks into an array and yields — progressive reveal

The source loaders run on the server: createCoordinatedLazy routes a config source to ChunkServerLoader (it never serializes the loader functions into a client component). A piece uses preloaded/precomputed data when it has it (props-context-layering); to load on the client, supply the source through a ChunkProvider instead (which lazily imports it).

A server Loader streams its content in under Suspense, with ChunkLoading as the coarse placeholder — so a low-res baseline lands in the SSR HTML and swaps to the detailed content once the server resolves it. (A data-mode source's quick initial() paints the same way, on the server.)

low-res preview — 9 points
import * as React from 'react';
import { createCoordinatedLazy } from '@mui/internal-docs-infra/CoordinatedLazy';
import type { Point } from './lineParts';
import FullChart from './FullChart';
import { SimpleChart } from './SimpleChart';

// A single chunk: the low-res baseline (`ChunkLoading`) is server-rendered into the
// SSR HTML, then the detailed line streams in from the server `Loader` under
// Suspense and swaps in once it resolves. The loader runs on the server (a config
// `source` would too); the browser only hydrates the streamed markup. For
// client-side loading instead, wrap the chunk in a `ChunkProvider`.
const ChartChunk = createCoordinatedLazy<{}, Point[]>({
  ChunkContent: FullChart,
  ChunkLoading: SimpleChart,
  Loader: () => import('./FullChart'),
});

export function InitialDetailedChart() {
  return (
    <ChartChunk />
  );
}

Keep initial() pure and isomorphic. It runs during render, so it must return the same value on the server and the client. Reading Date.now(), Math.random(), or window from it produces a hydration mismatch. The same applies to any user props you feed the content during loading.

When the full payload restates content the placeholder already has, ship it compressed and decode it against that content as a DEFLATE dictionary. Here the placeholder paints plain prose and hoists it as the dictionary; the full content is the same prose marked up with comment threads — compressed HAST whose text is already in the dictionary, so only the markup-and-comments delta crosses the fallback→content channel (the same trick the code highlighter uses):

The team gathers feedback before each release, then reviews every change together. Small fixes ship quickly, while larger proposals wait for a second opinion.

plain prose (dictionary)158 B
+ compressed comment delta284 B
= total serialized442 B
vs full document, raw713 B · 38% smaller
'use client';
import * as React from 'react';
import { CoordinatedLazy, useCoordinatedFallback } from '@mui/internal-docs-infra/CoordinatedLazy';
import { Replayable } from '@/components/Replayable/Replayable';
import { CommentLayer } from './CommentLayer';
import { DocumentView } from './DocumentView';
import { HOISTED, PROSE } from './documentData';

function Loading() {
  // Paint the plain prose and hoist it as the decompression dictionary, along
  // with the byte sizes the content reports. Rendering the same DocumentView the
  // content uses keeps the footer height constant — only the prose transitions.
  useCoordinatedFallback(HOISTED);
  return <DocumentView hoisted={HOISTED}>{PROSE}</DocumentView>;
}

function CommentedDocumentView() {
  const [ready, setReady] = React.useState(false);
  React.useEffect(() => {
    const id = setTimeout(() => setReady(true), 1400);
    return () => clearTimeout(id);
  }, []);

  // `requireHoist` holds the swap until the fallback hoists the prose, so the
  // content always has the dictionary it needs to decode the comments.
  return (
    <CoordinatedLazy ready={ready} requireHoist fallback={<Loading />} content={<CommentLayer />} />
  );
}

export function CommentedDocument() {
  return (
    <Replayable>
      <CommentedDocumentView />
    </Replayable>
  );
}

Build a self-loading CoordinatedLazy component. The returned component is isomorphic: per render it evaluates buildChunkRenderInputs and routes via resolveChunkRender, so one component covers build, server, and client loading, and server or client rendering:

  • content (preloaded/controlled) — renders ChunkContent directly, so build-precomputed data lands in the server HTML. ChunkContent may be a server OR client component here.
  • server-loader / server-initial — renders the server ChunkServerLoader under a Suspense boundary (server Loader/InitialLoader or a server-side data-mode load), so content loads and renders on the server and streams in. Requires a server (RSC) render context; supports server-component content.
  • client modes (async/initial/null) — delegates to the 'use client' CoordinatedLazyClient, which loads on the client and swaps the fallback to content. ChunkContent here must be a client component.

The client-mode branch hands the (function-bearing) config to a 'use client' component, so a client-loaded chunk must render inside a client subtree — call createCoordinatedLazy from a client module, or wrap it in a client provider (e.g. abstractCreateStream’s ClientProvider). Server-loaded and preloaded/precomputed chunks have no such constraint — they render entirely on the server path.

The user’s generic props T flow through to both components; data (type P) is the loaded value (or the initial value while loading). Use it standalone for any deferred piece (a demo, a chart, a code frame); useStream renders a streamed list of them.

PropertyTypeDescription
ChunkContent
React.ComponentType<ChunkContentProps<{}, P>>

The full content component.

ChunkLoading
| React.ComponentType<ChunkLoadingProps<{}, P>>
| undefined

The loading placeholder. Defaults to a component that renders null.

isLoaded
IsLoaded<P> | undefined

Whether the preloaded value suffices for the full content.

isInitial
IsInitial<P> | undefined

Whether the preloaded value suffices for the initial state.

source
StreamSource<P, O> | undefined

Data source (discriminated by mode). Its loader functions run on the server only — a data-mode source is executed by ChunkServerLoader (source.load for the full content, source.initial for a quick streamed paint) and never serialized into a Client Component. To load on the client, supply the source through a ChunkProvider (which lazily imports it) rather than this field. (Calling useChunk directly inside your own client component with a source is still fine — no server/client boundary is crossed there.)

Loader
| LazyComponentImport<ChunkContentProps<{}, P>>
| undefined

Server component rendered (under Suspense) to produce the full content. Always dynamically imported, and only imported when the render decision routes to it — so it never reaches the client bundle.

InitialLoader
| LazyComponentImport<ChunkContentProps<{}, P>>
| undefined

Server component rendered (under Suspense) to produce the initial state.

swap
ChunkSwapConfig | undefined

Swap timing forwarded to CoordinatedLazy.

loaderOptions
O | undefined

Default options passed to the source loaders.

contentManagesSwap
boolean | undefined

The ChunkContent component performs its own client-side loading and fallback->content swap. When set, the client-driven render modes render ChunkContent directly (with loading: true) instead of wrapping it in the framework’s useChunk+swap (CoordinatedLazyClient) — so a self-managing content (e.g. one already built on useCoordinatedSwap) is not double-swapped. Server and content/content-initial modes are unaffected.

revalidateOnIdle
boolean | undefined

Opt into stale-while-revalidate: once the chunk has loaded, automatically re-run the loader once on the first idle period (via requestIdleCallback) to refresh potentially-stale data in the background. Client-only. The chunk keeps showing its current data while the refresh is in flight.

Return Type
React.ComponentType<ChunkComponentProps<{}, P, O>>

useChunk

Loads a single piece's data on the client — from a directly-passed source, a surrounding ChunkProvider, or preloaded data — handling the quick initial value while the full data-mode load resolves. The component createCoordinatedLazy builds uses it internally for its client path (with a ChunkProvider source, since a config source runs on the server); call it directly with your own source for a custom client renderer.

Load a single chunk’s data on the client (props-context-layering: when the data already arrived via preloaded, no fetch happens). Handles the controlled/preloaded short-circuit and a quick initial value shown while the full data-mode load resolves.

Returns a refresh() that re-runs the loader with stale-while-revalidate, and (opt-in via config.revalidateOnIdle) schedules one such refresh on the first idle period after the chunk has loaded.

Used by the component createCoordinatedLazy produces; consumers can also call it directly for a custom chunk renderer.

ParameterTypeDescription
config
CreateChunkConfig<{}, P, O>
props
ChunkComponentProps<{}, P, O> | undefined
Return Type
UseChunkResult
KeyTypeRequired
data
P | undefined
Yes
loading
boolean
Yes
revalidating
boolean
Yes
refresh
() => Promise<void>
Yes

To stream a list of pieces in on the client and coordinate their swaps, use useStream.

Passing data from the fallback to the content

The placeholder paints a cheap version and hoists it; the content reads it back and refines it. This is how "quick now, detailed later" works without re-fetching (and what the compressed demo above uses).

// In the placeholder:
function Loading() {
  useCoordinatedFallback({ dictionary: 'HELLO' });
  return <PlainText />;
}

// In the content:
function Content() {
  const { dictionary } = useCoordinatedContent();
  return <Rich dictionary={dictionary} />;
}

Speculative preload

CoordinatedLazy fires a preload(hoisted) callback the moment the placeholder hoists, so a consumer can start importing heavy helpers the data implies — in parallel with loading the content. Pair it with the ChunkProvider's usePreload to dedup those imports across instances.

Server rendering

When a config has a server Loader/InitialLoader or a source, createCoordinatedLazy routes to ChunkServerLoader under a Suspense boundary automatically — so the loader runs and renders on the server (per-chunk Suspense) and streams in. ChunkServerLoader (and LazyContentServer, for a lazily-imported component) are plain async render functions with no Node-only imports, so they ship from this same entry and are simply inert on the client; you can also place either under your own Suspense boundary for a fully manual server path. Server rendering requires the component to render in a server (RSC) context.

LazyContent

LazyContent lazily imports a component (server or client) and renders it once its chunk has loaded, reporting readiness to the settle gate — so a heavy content component can be code-split without breaking the coordinated swap. While the chunk loads it shows a fallback (or, inside a swap, the coordinating fallback), so the placeholder keeps covering the load with no empty flash. The widget's code is fetched only when it mounts — load it and watch the skeleton hold until the chunk lands:

LazyWidget.tsx
'use client';
import * as React from 'react';
import { CoordinatedLazy, LazyContent } from '@mui/internal-docs-infra/CoordinatedLazy';
import { Replayable } from '@/components/Replayable/Replayable';

// Matches the widget's footprint so revealing it doesn't shift the layout.
function Skeleton() {
  return (
    <div
      style={{
        width: 280,
        boxSizing: 'border-box',
        display: 'flex',
        flexDirection: 'column',
        gap: 12,
        padding: 16,
        borderRadius: 8,
        border: '1px solid #d0cdd7',
        background: '#faf9fc',
      }}
    >
      {/* Each row matches the widget's row height (20 / 44 / 18) so revealing it
          doesn't shift the layout. */}
      <div style={{ height: 20, display: 'flex', alignItems: 'center' }}>
        <div style={{ width: '55%', height: 14, borderRadius: 4, background: '#e7e4ee' }} />
      </div>
      <div style={{ display: 'flex', gap: 8 }}>
        {[0, 1, 2].map((index) => (
          <div
            key={index}
            style={{
              boxSizing: 'border-box',
              width: 44,
              height: 44,
              borderRadius: 8,
              background: '#e7e4ee',
            }}
          />
        ))}
      </div>
      <div style={{ height: 18, display: 'flex', alignItems: 'center' }}>
        <div style={{ width: '40%', height: 12, borderRadius: 4, background: '#e7e4ee' }} />
      </div>
    </div>
  );
}

function LazyWidgetView() {
  const [ready, setReady] = React.useState(false);

  // The swap shows the skeleton until `ready`, then reveals its `content`. That
  // content is a `LazyContent`, so its chunk is fetched only once the swap mounts
  // it: it keeps the *same* skeleton up (the coordinating fallback, so no explicit
  // `fallback` is needed) until the code lands, then reports readiness — and the
  // swap settles as one coordinated step rather than flashing an empty box.
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 12, alignItems: 'flex-start' }}>
      <button
        type="button"
        onClick={() => setReady(true)}
        disabled={ready}
        style={{
          font: '13px sans-serif',
          padding: '6px 12px',
          borderRadius: 6,
          cursor: ready ? 'default' : 'pointer',
          border: '1px solid #7c3aed',
          background: ready ? '#f3eefe' : '#7c3aed',
          color: ready ? '#7c3aed' : '#fff',
        }}
      >
        {ready ? 'Widget loaded' : 'Load the widget'}
      </button>
      <CoordinatedLazy
        ready={ready}
        fallback={<Skeleton />}
        content={<LazyContent content={() => import('./HeavyWidget')} />}
      />
    </div>
  );
}

export function LazyWidget() {
  return (
    <Replayable label="Reset">
      <LazyWidgetView />
    </Replayable>
  );
}

Because the placeholder already hoisted its cheap data, the content can also decide not to load the heavy path at all — and then the code-split chunk is never even imported. A natural case is annotations that only exist on preview deployments: in production the content reuses the hoisted plaintext as-is (no compressed payload, no renderer chunk); on a preview it loads the comments through LazyContent. Toggle between the two:

The team gathers feedback before each release, then reviews every change together. Small fixes ship quickly, while larger proposals wait for a second opinion.

production · comments skipped — +0 B over the wire, renderer chunk not loaded
'use client';
import * as React from 'react';
import {
  CoordinatedLazy,
  LazyContent,
  useCoordinatedContent,
  useCoordinatedFallback,
} from '@mui/internal-docs-infra/CoordinatedLazy';
import { DemoButton } from '@/components/DemoButton/DemoButton';
import { DocumentView } from './DocumentView';
import { HOISTED, PROSE, type Hoisted } from './documentData';

function ProductionNote() {
  return (
    <div style={{ font: '13px monospace', color: '#3f8f3f' }}>
      production · comments skipped — +0 B over the wire, renderer chunk not loaded
    </div>
  );
}

function Loading() {
  // Always paint the plain prose and hoist it as the dictionary — the cheap layer
  // both deployments share.
  useCoordinatedFallback(HOISTED);
  return (
    <DocumentView hoisted={HOISTED} footer={<ProductionNote />}>
      {PROSE}
    </DocumentView>
  );
}

function ProductionContent() {
  // Reuse the hoisted plaintext — no compressed payload decoded, no comment
  // renderer imported. The full content is the dictionary the fallback already had.
  const hoisted = useCoordinatedContent() as Hoisted;
  return (
    <DocumentView hoisted={hoisted} footer={<ProductionNote />}>
      {hoisted.dictionary}
    </DocumentView>
  );
}

function Toggle({ preview, onChange }: { preview: boolean; onChange: (next: boolean) => void }) {
  const active = { background: '#7c3aed', color: '#fff' };
  return (
    <div style={{ display: 'flex', gap: 6 }}>
      <DemoButton style={!preview ? active : undefined} onClick={() => onChange(false)}>
        Production
      </DemoButton>
      <DemoButton style={preview ? active : undefined} onClick={() => onChange(true)}>
        Preview
      </DemoButton>
    </div>
  );
}

export function ConditionalComments() {
  const [preview, setPreview] = React.useState(false);

  // The fallback hoists the plaintext either way. On the preview path the content
  // is a code-split `LazyContent` that imports the comment renderer and decodes the
  // compressed comments; on production the content just reuses the hoisted
  // plaintext — so neither the payload nor the renderer chunk is ever loaded.
  const content = preview ? (
    <LazyContent content={() => import('./CommentLayerChunk')} />
  ) : (
    <ProductionContent />
  );

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
      <Toggle preview={preview} onChange={setPreview} />
      <CoordinatedLazy ready requireHoist fallback={<Loading />} content={content} />
    </div>
  );
}

Lazily import a component and render it once its chunk has loaded, reporting readiness to the settle gate — so the page can coordinate the swap and a StreamController can reflect it in loading.

The import runs in an effect (not React.lazy + Suspense) on purpose: the swap that reveals this content mounts/unmounts the subtree around a pending import(), and a Suspense boundary that comes and goes around a pending promise trips React’s async-info-on-boundary tracking (“cleaning up async info that was not on the parent Suspense boundary”). Loading in an effect avoids a Suspense boundary entirely, and renders only the fallback during SSR (effects don’t run there).

The import factory is captured once (via lazy useState), so an inline content={() => import('./X')} doesn’t restart the import every render. While the module loads, the explicit fallback (or, if none, the coordinating swap’s fallback from CoordinatedContentContext) is shown — so the same placeholder keeps covering the load, with no empty flash.

PropTypeDescription
content
LazyComponentImport<{}>

Dynamic import of the component to render.

fallback
React.ReactNode | undefined

Placeholder shown while the module loads. Defaults to null.

gate
SettleGate | undefined

Additional settle gate to report readiness to once the component has loaded and mounted (e.g. a useStream controller gate). The page-global gate is always registered too. Client path only — the server path streams via Suspense and has no client gate to report to.

props
{} | undefined

Props forwarded to the imported component.

Advanced: building a custom swap

CoordinatedLazy is a thin wrapper around useCoordinatedSwap. Use the hook directly when the swap must fold the hoisted data into its own ready computation (as the code highlighter does to decompress before swapping).

Reference

CoordinatedLazy

Show fallback until ready (and the page-coordinated swap conditions) are met, then swap to content. The fallback is force-mounted once so its useCoordinatedFallback hoist runs even when the content is precomputed; the hoisted data is handed down to content via useCoordinatedContent.

Generalizes the state-driven fallback<->content swap from CodeHighlighterClient. Advanced consumers that need to fold the hoisted data into their own ready computation should use useCoordinatedSwap directly instead of this component.

PropTypeDescription
awaitContent
boolean | undefined

Hold the swap until the content reports it has loaded, mounting the content behind the fallback so a code-split (e.g. LazyContent) content can load in the background and reveal only once its chunk has arrived. See .

content
React.ReactNode

Full content, shown after the swap. Pre-rendered on the server.

data
Record<string, unknown> | undefined

Arbitrary parent->fallback data exposed to the fallback subtree.

defer
boolean | undefined

Hold the swap while real async work is in flight even though ready.

fallback
React.ReactNode | undefined

Loading placeholder; force-mounted once so its hoist hook runs.

gate
SettleGate | undefined

Settle gate to register this swap with. When omitted, the ambient gate from a surrounding coordinator (e.g. the useStream controller, via CoordinatedGateContext) is used instead. The page-global gate is always registered on top of either, so a page-wide coordinated commit waits for the swap regardless.

holdGate
boolean | undefined

Hold the settle gate open without re-showing the fallback (content stays rendered). See .

preload
| ((hoisted: Record<string, unknown>) => void)
| undefined

Speculative preload hook. See : fired with the hoisted data so the consumer can start dynamic imports of helpers in parallel with loading the full content.

ready
boolean

Whether the content’s data is ready to display.

requireHoist
boolean | undefined

Hold the swap until the fallback hoists at least once.

skipFallback
boolean | undefined

Skip the fallback entirely.

useCoordinatedFallback

Called by a fallback (loading) component to optionally hoist data the full content will need and to signal that it mounted. Returns the parent->fallback data and whether this fallback’s CoordinatedLazy is nested inside an outer, still-loading one. Generalizes useCodeFallback.

Pass a memoized hoistData map; each entry is hoisted up to the swap, where it is folded into the consumer’s ready decision and handed down to the full content via useCoordinatedContent.

ParameterTypeDescription
hoistData
Record<string, unknown> | undefined
Return Type
UseCoordinatedFallbackResult
KeyTypeRequired
data
Record<string, unknown> | undefined
No
isNested
boolean
Yes

useCoordinatedContent

Read the data the fallback hoisted, from inside the full content. Lets the content use what the fallback fetched (e.g. a decompression dictionary) without the consumer threading it through props. Returns an empty map when rendered outside a CoordinatedLazy.

Return Type
Record<string, unknown>

useCoordinatedSwap

The generalized fallback<->content swap state machine extracted from CodeHighlighterClient. Decides whether to show the fallback or the content, owns the force-mount-once behavior, collects data hoisted up from the fallback, suppresses nested-fallback flicker, and registers with a settle gate so the page can coordinate when every initial swap has landed.

showFallback is the generalization of isFallbackRendered:

hasFallback && !skipFallback && (
  !ready || defer || isNested || !fallbackMounted || (requireHoist && !hasHoisted)
)
PropertyTypeDescription
ready
boolean

Whether the content’s data is ready to display.

defer
boolean | undefined

Hold the swap while real async work is still in flight even though ready.

holdGate
boolean | undefined

Hold the settle gate open WITHOUT re-showing the fallback — the content stays rendered while the page-wide coordination waits. Unlike defer (which holds the rendered fallback), this only affects gate registration, for content that has swapped in but is still finishing deferred work it gates internally (e.g. the code highlighter rendering plain text, then highlighting in place).

hasFallback
boolean

Whether a fallback element exists to show.

skipFallback
boolean | undefined

Skip the fallback entirely.

requireHoist
boolean | undefined

Additionally hold the swap until the fallback hoists at least once.

awaitContent
boolean | undefined

Hold the swap until the content reports it has loaded. The consumer mounts the content (e.g. a LazyContent) while the fallback is shown so it can load in the background, returning null until ready and then calling the content context’s reportReady — so a code-split content component loads behind the placeholder and reveals only once its chunk has arrived.

gate
SettleGate | undefined

Settle gate to register this swap with. When omitted, the ambient gate from a surrounding coordinator (e.g. the useStream controller, via CoordinatedGateContext) is used instead. The page-global gate is always registered on top of either, so a page-wide coordinated commit waits for the swap regardless.

data
Record<string, unknown> | undefined

Arbitrary parent->fallback data exposed via the fallback context.

preload
| ((hoisted: Record<string, unknown>) => void)
| undefined

Fired as soon as the fallback hoists data, with the hoisted map. Lets the consumer kick off dynamic import()s of heavy helpers it can tell from the data it will need — in parallel with loading the full content, instead of the content mounting and then requesting them in a serial roundtrip. Should be idempotent (the module cache dedups within a graph); cross-instance dedup is the layout provider’s job.

Return Type
UseCoordinatedSwapResult
KeyTypeRequired
showFallback
boolean
Yes
fallbackContext
CoordinatedFallbackContextValue
Yes
hoisted
Record<string, unknown>
Yes
loading
boolean
Yes
contentReady
boolean
Yes
reportContentReady
() => void
Yes
hoist
(key: string, value: unknown) => void
Yes
ChunkComponentProps

Props accepted by the component returned from createCoordinatedLazy.

type ChunkComponentProps<T extends {} = {}, P = unknown, O = unknown> = {
  /** Build-time/precomputed value for this chunk. */
  preloaded?: P;
  /** Authoritative/controlled value: render content directly, never the loaders. */
  controlled?: boolean;
  /**
   * Force client-side rendering: ignore the server `Loader`/`InitialLoader` for
   * this render so the decision routes to a content/client branch instead. Lets
   * a consumer that *configures* server loaders statically opt out of them
   * per-render (e.g. when no server loading functions are available, or the
   * caller explicitly wants the client to drive). Has no effect once
   * `isLoaded`/`controlled` already render the content.
   */
  forceClient?: boolean;
  /**
   * Per-render override for the `isInitial` decision input (whether the initial
   * paint is already in hand). Mirrors how `controlled` overrides `isLoaded`:
   * lets a consumer whose initial-readiness depends on per-render context it
   * cannot express as a pure `config.isInitial(preloaded)` predicate compute it
   * in its own router and pass the result. Takes precedence over
   * `config.isInitial`.
   */
  isInitial?: boolean;
  /**
   * For the server render modes (`server-loader`/`server-initial`), block the
   * server render on the loader instead of streaming a fallback: render the
   * server loader *without* a Suspense boundary, so its content lands in the
   * initial HTML (e.g. for no-JS / crawler SSR). When unset (the default) the
   * loader streams under Suspense, showing `ChunkLoading` until it resolves.
   */
  awaitServerLoad?: boolean;
  /**
   * Skip the initial-loader stage: ignore the `InitialLoader` / source-`initial`
   * for this render so a not-yet-loaded chunk loads the full content directly
   * rather than fetching a quick initial first. For consumers that have no
   * loading UI to show an initial paint into (so a 2-stage initial->full load
   * would be wasted).
   */
  skipInitialLoad?: boolean;
  /** Per-render loader options (merged over the config's `loaderOptions`). */
  loaderOptions?: O;
  /** User generic props forwarded to `ChunkContent` / `ChunkLoading`. */
  userProps?: T;
  /** Settle gate to register with (defaults to the surrounding controller / page). */
  gate?: SettleGate;
}
ChunkContentProps

Props the full chunk content receives. Mirrors ContentProps<T>: the user’s generic props T are merged in, plus the resolved data (of type P) and a loading flag (false once the full data is in).

type ChunkContentProps<T extends {} = {}, P = unknown> = {
  data?: P;
  loading: boolean;
  refresh?: () => Promise<void>;
  revalidating?: boolean;
} & T
ChunkLoadingProps

Props the loading placeholder receives. Mirrors ContentLoadingProps<T>: loading is always true, and data carries whatever initial/preloaded value is available (of type P).

type ChunkLoadingProps<T extends {} = {}, P = unknown> = { data?: P; loading: true } & T
ChunkRenderDecision

Result of resolveChunkRender.

type ChunkRenderDecision = { mode: ChunkRenderMode; loading: boolean }
ChunkRenderInputs

Already-evaluated inputs to resolveChunkRender (decoupled from config shape).

type ChunkRenderInputs = {
  /** Evaluated `isLoaded(preloaded)` (or the controlled override). */
  isLoaded: boolean;
  /** Evaluated `isInitial(preloaded)`. */
  isInitial: boolean;
  /** A server initial is configured: an `InitialLoader`, or a `data`-mode `source.initial`. */
  hasServerInitial: boolean;
  /** A server full loader is configured: a `Loader`, or a `data`-mode `source.load`. */
  hasServerLoader: boolean;
}
ChunkRenderMode

The branch of the render decision that applies for a chunk.

type ChunkRenderMode =
  | 'content'
  | 'content-initial'
  | 'server-initial'
  | 'server-loader'
  | 'attempt-initial-client'
ChunkSwapConfig

Swap timing forwarded to the underlying CoordinatedLazy.

type ChunkSwapConfig = { defer?: boolean; requireHoist?: boolean; channelKey?: string | null }
CoordinatedContentContext

Carries the data the fallback hoisted down into the full content. A CoordinatedLazy provides it around content after the swap; content reads it via useCoordinatedContent.

CoordinatedContentContextValue

The data the fallback hoisted, handed down to the full content so it can use what the fallback fetched (e.g. a DEFLATE dictionary). Read via useCoordinatedContent.

type CoordinatedContentContextValue = {
  hoisted: Record<string, unknown>;
  /**
   * The content calls this once it has loaded (its dynamic import resolved), so
   * the swap can register readiness with the settle gate. Used by `LazyContent`.
   */
  reportReady?: () => void;
  /**
   * The loading fallback to show *while a dynamically-imported content loads*.
   * After the swap reveals the content, a `LazyContent` shows this as its own
   * Suspense fallback during the `import()` - so the same placeholder the swap
   * showed keeps covering the load, with no empty flash. Generalizes "hand the
   * `ContentLoading` to the lazy content".
   */
  fallback?: React.ReactNode;
}
CoordinatedFallbackContext

Provided by a CoordinatedLazy to its fallback subtree while the fallback is shown. Carries the upward hoist channel and the nested-suppression flag.

undefined outside a fallback subtree: a fallback reads it via useCoordinatedFallback, and a nested CoordinatedLazy detects its presence to know it is rendered inside an outer instance’s still-loading fallback.

CoordinatedFallbackContextValue

Provided by a CoordinatedLazy to its fallback subtree while the fallback is shown. Carries the upward hoist channel and the nested-suppression flag. Generalizes CodeHighlighterFallbackContext ({ extraVariants, setFallbackHasts, onHookCalled }).

type CoordinatedFallbackContextValue = {
  /**
   * Hoist a keyed value up to the swap so it can be folded into the consumer's
   * `ready` decision and handed down to the full content via
   * [`CoordinatedContentContextValue`](#coordinatedcontentcontextvalue). Generalizes `setFallbackHasts`.
   */
  hoist?: (key: string, value: unknown) => void;
  /**
   * Signal that the fallback's hoist hook ran. The generic swap force-mounts
   * the fallback on its own, so this is optional - consumers (e.g.
   * `CodeHighlighter`) use it to validate that the loading component wired its
   * hoist hook. Generalizes `onHookCalled`.
   */
  onReady?: () => void;
  /**
   * `true` when this instance is nested inside an outer `CoordinatedLazy` still
   * showing its own fallback. The inner stays in fallback while set, collapsing
   * a "fallback -> content -> fallback -> content" flicker into one transition.
   * Generalizes `isNestedInsideOuterFallback`.
   */
  isNested?: boolean;
  /** Arbitrary parent->fallback data (generalizes `extraVariants`). */
  data?: Record<string, unknown>;
}
CoordinatedGateContext

The ambient settle gate that a CoordinatedLazy swap registers with when it isn’t given an explicit gate prop. A coordinator (e.g. the useStream controller) provides its gate here so every swap rendered beneath it reports into the same gate — that is how a group’s loading reflects each piece’s swap without threading a gate prop through every one. null outside any coordinator, in which case the swap registers only with the page-global gate.

type CoordinatedGateContext = React.Context<SettleGate | null>
CoordinatedLazyProps

Props for CoordinatedLazy.

type CoordinatedLazyProps = {
  /** Full content, shown after the swap. Pre-rendered on the server. */
  content: React.ReactNode;
  /** Loading placeholder; force-mounted once so its hoist hook runs. */
  fallback?: React.ReactNode;
  /** Whether the content's data is ready to display. */
  ready: boolean;
  /** Hold the swap while real async work is in flight even though `ready`. */
  defer?: boolean;
  /**
   * Hold the settle gate open without re-showing the fallback (content stays
   * rendered). See .
   */
  holdGate?: boolean;
  /** Skip the fallback entirely. */
  skipFallback?: boolean;
  /** Hold the swap until the fallback hoists at least once. */
  requireHoist?: boolean;
  /**
   * Hold the swap until the content reports it has loaded, mounting the content
   * behind the fallback so a code-split (e.g. `LazyContent`) content can load in
   * the background and reveal only once its chunk has arrived. See
   * .
   */
  awaitContent?: boolean;
  /**
   * Settle gate to register this swap with. When omitted, the ambient gate from
   * a surrounding coordinator (e.g. the `useStream` controller, via
   * [`CoordinatedGateContext`](#coordinatedgatecontext)) is used instead. The page-global gate is
   * always registered on top of either, so a page-wide coordinated commit waits
   * for the swap regardless.
   */
  gate?: SettleGate;
  /** Arbitrary parent->fallback data exposed to the fallback subtree. */
  data?: Record<string, unknown>;
  /**
   * Speculative preload hook. See :
   * fired with the hoisted data so the consumer can start dynamic imports of
   * helpers in parallel with loading the full content.
   */
  preload?: (hoisted: Record<string, unknown>) => void;
}
CreateChunkConfig

Configuration for createCoordinatedLazy.

type CreateChunkConfig<T extends {} = {}, P = unknown, O = unknown> = {
  /** The full content component. */
  ChunkContent: React.ComponentType<ChunkContentProps<T, P>>;
  /** The loading placeholder. Defaults to a component that renders `null`. */
  ChunkLoading?: React.ComponentType<ChunkLoadingProps<T, P>>;
  /** Whether the preloaded value suffices for the full content. */
  isLoaded?: IsLoaded<P>;
  /** Whether the preloaded value suffices for the initial state. */
  isInitial?: IsInitial<P>;
  /**
   * Data source (discriminated by `mode`). Its loader functions run **on the
   * server only** - a `data`-mode source is executed by `ChunkServerLoader`
   * (`source.load` for the full content, `source.initial` for a quick streamed
   * paint) and never serialized into a Client Component. To load on the *client*,
   * supply the source through a `ChunkProvider` (which lazily imports it)
   * rather than this field. (Calling `useChunk` directly inside your own client
   * component with a `source` is still fine - no server/client boundary is
   * crossed there.)
   */
  source?: StreamSource<P, O>;
  /**
   * Server component rendered (under Suspense) to produce the full content.
   * Always dynamically imported, and only imported when the render decision
   * routes to it - so it never reaches the client bundle.
   */
  Loader?: LazyComponentImport<ChunkContentProps<T, P>>;
  /** Server component rendered (under Suspense) to produce the initial state. */
  InitialLoader?: LazyComponentImport<ChunkContentProps<T, P>>;
  /** Swap timing forwarded to `CoordinatedLazy`. */
  swap?: ChunkSwapConfig;
  /** Default options passed to the source loaders. */
  loaderOptions?: O;
  /**
   * The `ChunkContent` component performs its own client-side loading and
   * fallback->content swap. When set, the client-driven render modes render
   * `ChunkContent` directly (with `loading: true`) instead of wrapping it in the
   * framework's [`useChunk`](#usechunk)+swap (`CoordinatedLazyClient`) - so a
   * self-managing content (e.g. one already built on `useCoordinatedSwap`) is not
   * double-swapped. Server and content/`content-initial` modes are unaffected.
   */
  contentManagesSwap?: boolean;
  /**
   * Opt into stale-while-revalidate: once the chunk has loaded, automatically
   * re-run the loader once on the first idle period (via `requestIdleCallback`)
   * to refresh potentially-stale data in the background. Client-only. The chunk
   * keeps showing its current data while the refresh is in flight.
   */
  revalidateOnIdle?: boolean;
}
LazyContentProps

Props for LazyContent / LazyContentServer.

type LazyContentProps<T extends {} = {}> = {
  /** Dynamic import of the component to render. */
  content: LazyComponentImport<T>;
  /** Props forwarded to the imported component. */
  props?: T;
  /** Placeholder shown while the module loads. Defaults to `null`. */
  fallback?: React.ReactNode;
  /**
   * Additional settle gate to report readiness to once the component has loaded
   * and mounted (e.g. a `useStream` controller gate). The page-global gate is
   * always registered too. Client path only - the server path streams via
   * Suspense and has no client gate to report to.
   */
  gate?: SettleGate;
}
StreamSource

Where a chunk’s data comes from — a discriminated union on mode, so each strategy is strongly typed with no overloads or runtime return-type sniffing:

  • 'data' — load the chunk’s data directly (optionally with a quick initial value first).
  • 'urls' — split into per-chunk URLs (loadUrls), then load each URL’s data (loadChunk); supports an initial pass.
  • 'stream' — push chunks into the passed array over time and yield after each, for progressive reveal (the generator’s return is the last-chunk signal).
type StreamSource<P = unknown, O = unknown> =
  | {
      mode: 'data';
      load: (options: O, signal: AbortSignal) => Promise<P>;
      initial?: (options: O) => P;
    }
  | {
      mode: 'urls';
      loadUrls: (options: O, signal: AbortSignal) => Promise<StreamUrlsResult>;
      loadChunk: (url: URL, options: O, signal: AbortSignal) => Promise<P>;
      initialUrls?: (options: O) => StreamUrlsResult;
      initialChunk?: (url: URL, options: O) => P;
    }
  | {
      mode: 'stream';
      stream: (chunks: P[], options: O, signal: AbortSignal) => AsyncGenerator<void, void, void>;
    }
StreamUrlsResult

Result of a urls-mode loader: the chunk URLs to load individually, rather than the data itself. lastChunk marks the final URL for last-chunk completion when the total isn’t known up front.

type StreamUrlsResult = { chunks: URL[]; lastChunk?: boolean }
UseChunkResult

Result of useChunk.

type UseChunkResult<P> = {
  /** The chunk's data: the loaded value, or the initial/preloaded value while loading. */
  data: P | undefined;
  /** `true` until the full data has loaded. */
  loading: boolean;
  /** `true` while a background refresh is in flight; the current `data` stays visible. */
  revalidating: boolean;
  /**
   * Re-run the `data`-mode loader and swap in fresh data, keeping the current
   * data visible meanwhile (stale-while-revalidate). Aborts any prior in-flight
   * refresh. A no-op for non-`data` sources or when no source resolves.
   */
  refresh: () => Promise<void>;
}
UseCoordinatedFallbackResult
type UseCoordinatedFallbackResult = {
  /** Parent->fallback data provided via . */
  data?: Record<string, unknown>;
  /** Whether this fallback's `CoordinatedLazy` is nested inside an outer, still-loading one. */
  isNested: boolean;
}
UseCoordinatedSwapOptions

Options for useCoordinatedSwap.

type UseCoordinatedSwapOptions = {
  /** Whether the content's data is ready to display. */
  ready: boolean;
  /** Hold the swap while real async work is still in flight even though `ready`. */
  defer?: boolean;
  /**
   * Hold the settle gate open WITHOUT re-showing the fallback - the content stays
   * rendered while the page-wide coordination waits. Unlike `defer` (which holds
   * the rendered fallback), this only affects gate registration, for content that
   * has swapped in but is still finishing deferred work it gates internally (e.g.
   * the code highlighter rendering plain text, then highlighting in place).
   */
  holdGate?: boolean;
  /** Whether a fallback element exists to show. */
  hasFallback: boolean;
  /** Skip the fallback entirely. */
  skipFallback?: boolean;
  /** Additionally hold the swap until the fallback hoists at least once. */
  requireHoist?: boolean;
  /**
   * Hold the swap until the content reports it has loaded. The consumer mounts
   * the content (e.g. a `LazyContent`) while the fallback is shown so it can
   * load in the background, returning `null` until ready and then calling the
   * content context's `reportReady` - so a code-split content component loads
   * behind the placeholder and reveals only once its chunk has arrived.
   */
  awaitContent?: boolean;
  /**
   * Settle gate to register this swap with. When omitted, the ambient gate from
   * a surrounding coordinator (e.g. the `useStream` controller, via
   * [`CoordinatedGateContext`](#coordinatedgatecontext)) is used instead. The page-global gate is
   * always registered on top of either, so a page-wide coordinated commit waits
   * for the swap regardless.
   */
  gate?: SettleGate;
  /** Arbitrary parent->fallback data exposed via the fallback context. */
  data?: Record<string, unknown>;
  /**
   * Fired as soon as the fallback hoists data, with the hoisted map. Lets the
   * consumer kick off dynamic `import()`s of heavy helpers it can tell from the
   * data it will need - in parallel with loading the full content, instead of
   * the content mounting and then requesting them in a serial roundtrip. Should
   * be idempotent (the module cache dedups within a graph); cross-instance
   * dedup is the layout provider's job.
   */
  preload?: (hoisted: Record<string, unknown>) => void;
}
UseCoordinatedSwapResult
type UseCoordinatedSwapResult = {
  /** Whether the fallback branch should mount this render. */
  showFallback: boolean;
  /** Context value to provide to the fallback subtree. */
  fallbackContext: CoordinatedFallbackContextValue;
  /** Data hoisted up from the fallback so far, keyed. */
  hoisted: Record<string, unknown>;
  /** `true` while the fallback is being shown. */
  loading: boolean;
  /** In `awaitContent` mode, whether the content has reported it loaded. */
  contentReady: boolean;
  /** Passed to the content (via the content context) so it can report it loaded. */
  reportContentReady: () => void;
  /**
   * Hoist a keyed value up to the swap directly (the same channel the fallback's
   * `useCoordinatedFallback` uses). Lets the consumer populate the hoisted map
   * from outside the fallback subtree - e.g. a client-loaded data path that has
   * no fallback mounted but still needs to feed the hoisted dictionary.
   */
  hoist: (key: string, value: unknown) => void;
}