facebook/react · error · Error

426

426

Error message

A component suspended while responding to synchronous input. This will cause the UI to be replaced with a loading indicator. To fix, updates that suspend should be wrapped with startTransition.

What it means

renderToStringImpl powers the legacy synchronous renderers (renderToString/renderToStaticMarkup). It starts a Fizz request, aborts still-pending Suspense boundaries, and then checks the onShellReady flag; if the shell never became ready — something suspended and blocked synchronous completion — it throws this message (code 426). The source comments themselves note the client-worded message does not really fit here and the legacy renderer is slated for deletion.

Source

Thrown at packages/react-dom/src/server/ReactDOMLegacyServerImpl.js:94

    onShellReady,
    undefined,
    undefined,
    undefined,
  );
  startWork(request);
  // If anything suspended and is still pending, we'll abort it before writing.
  // That way we write only client-rendered boundaries from the start.
  abort(request, abortReason);
  startFlowing(request, destination);
  if (didFatal && fatalError !== abortReason) {
    throw fatalError;
  }

  if (!readyToStream) {
    // Note: This error message is the one we use on the client. It doesn't
    // really make sense here. But this is the legacy server renderer, anyway.
    // We're going to delete it soon.
    throw new Error(
      'A component suspended while responding to synchronous input. This ' +
        'will cause the UI to be replaced with a loading indicator. To fix, ' +
        'updates that suspend should be wrapped with startTransition.',
    );
  }

  return result;
}

export {renderToStringImpl, ReactVersion as version};

View on GitHub (pinned to eafeac097b)

Solutions

  1. Move to renderToPipeableStream (Node) or renderToReadableStream (edge/browser), which support Suspense and streaming
  2. Wrap suspending components in <Suspense fallback={...}> so the shell completes without them
  3. Preload or await the data before calling renderToString so nothing suspends
  4. Keep lazy()/async resources out of the shell level of the tree

Example fix

// before
const html = renderToString(<App />); // App suspends in the shell

// after
// 1) add a Suspense boundary around the suspending part
<Suspense fallback={<Loading />}><SlowContent /></Suspense>
// 2) or use the streaming API
const {pipe} = renderToPipeableStream(<App />, {onShellReady() { pipe(res); }});
Defensive patterns

Strategy: try-catch

Try / catch

let html;
try {
  html = renderToString(<App />);
} catch (e) {
  if (
    e instanceof Error &&
    e.message.includes('A component suspended while responding to synchronous input')
  ) {
    throw new Error(
      'renderToString blocked on Suspense — wrap suspending content in <Suspense> or switch to renderToPipeableStream'
    );
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling renderToString() or renderToStaticMarkup() on a tree whose shell suspends and cannot finish synchronously: a lazy() component, awaited promise (use()), or data fetch above any <Suspense> boundary, so onShellReady never fires before the abort.

Common situations: Legacy SSR pipelines feeding renderToString output into page templates; email/HTML generation with components that newly added suspenseful data loading; upgrading trees whose top level now suspends.

Related errors


AI-assisted analysis of facebook/react@eafeac097b (2026-08-21). Data as JSON: /api/errors/a84d9f1156431410. Report an issue: GitHub.