facebook/react · error · Error

558

558

Error message

Client rendering an Activity suspended it again. This is a bug in React.

What it means

Thrown while completing an Activity component during hydration. If hydrating an <Activity> boundary fails, React retries it with client rendering (retryActivityComponentWithoutHydrating); that retry is expected to render synchronously without a suspense handler. If the second, hydration-free pass suspends again (DidCapture flag set), this invariant fires — React would loop forever otherwise.

Solutions

  1. Fix the underlying hydration mismatch inside the Activity boundary so the client retry never has to run.
  2. Ensure children of a mismatched Activity boundary can render without suspending on the client (preload lazy components / warm caches before hydrate).
  3. Upgrade to the latest canary React — Activity hydration is actively worked on; if it persists, file an issue with a repro.
Defensive patterns

Strategy: try-catch

Try / catch

// ErrorBoundary is the only runtime containment; fix the mismatch underneath it
class ActivityBoundary extends React.Component {
  state = {failed: false};
  static getDerivedStateFromError() { return {failed: true}; }
  render() { return this.state.failed ? this.props.fallback : this.props.children; }
}

Prevention

When it happens

Trigger: An <Activity> subtree fails hydration (mismatched markup) AND its children suspend again during the client-render retry — e.g., a lazy component or suspending data fetch inside <Activity mode="hidden"> whose server HTML doesn't match or is missing.

Common situations: Apps using the new <Activity> component together with streaming SSR, lazy() boundaries, or suspense-backed data caches, where hydration mismatches and suspending children coincide. Mostly seen on experimental/canary React builds.

Related errors


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

Appendix: source

Thrown at packages/react-reconciler/src/ReactFiberCompleteWork.js:1538

          if (workInProgress.flags & ForceClientRender) {
            popSuspenseHandler(workInProgress);
            // Special case. There were remaining unhydrated nodes. We treat
            // this as a mismatch. Revert to client rendering.
            return workInProgress;
          } else {
            popSuspenseHandler(workInProgress);
            // Did not finish hydrating, either because this is the initial
            // render or because something suspended.
            return null;
          }
        }

        if ((workInProgress.flags & DidCapture) !== NoFlags) {
          // We called retryActivityComponentWithoutHydrating and tried client rendering
          // but now we suspended again. We should never arrive here because we should
          // not have pushed a suspense handler during that second pass and it should
          // instead have suspended above.
          throw new Error(
            'Client rendering an Activity suspended it again. This is a bug in React.',
          );
        }

        // Continue with the normal Activity path.
      }

      bubbleProperties(workInProgress);
      return null;
    }
    case SuspenseComponent: {
      const nextState: null | SuspenseState = workInProgress.memoizedState;

      // Special path for dehydrated boundaries. We may eventually move this
      // to its own fiber type so that we can add other kinds of hydration
      // boundaries that aren't associated with a Suspense tree. In anticipation
      // of such a refactor, all the hydration logic is contained in
      // this branch.

View on GitHub (pinned to eafeac097b)