facebook/react · warning · Error

488

488

Error message

Couldn't find all resumable slots by key/index during replaying. The tree doesn't match so React will fallback to client rendering.

What it means

When prerendered SSR output is resumed, React walks recorded replay 'slots' keyed by type and key/index. In replaySuspenseBoundary the server replays a previously postponed Suspense boundary's content; if the replay pass finishes while pendingTasks is still 1 and replay nodes remain, some recorded slots were never matched. React throws error code 488 so the boundary falls back to client rendering instead of emitting a mismatched tree.

Source

Thrown at packages/react-server/src/ReactFizzServer.js:1837

  // We can reuse the current context and task to render the content immediately without
  // context switching. We just need to temporarily switch which boundary and replay node
  // we're writing to. If something suspends, it'll spawn new suspended task with that context.
  task.blockedBoundary = resumedBoundary;
  task.hoistableState = resumedBoundary.contentState;
  task.keyPath = keyPath;
  task.formatContext = getSuspenseContentFormatContext(
    request.resumableState,
    prevContext,
  );
  task.row = null;
  task.replay = {nodes: childNodes, slots: childSlots, pendingTasks: 1};

  try {
    // We use the safe form because we don't handle suspending here. Only error handling.
    renderNode(request, task, content, -1);

    if (task.replay.pendingTasks === 1 && task.replay.nodes.length > 0) {
      throw new Error(
        "Couldn't find all resumable slots by key/index during replaying. " +
          "The tree doesn't match so React will fallback to client rendering.",
      );
    }
    task.replay.pendingTasks--;
    if (
      resumedBoundary.pendingTasks === 0 &&
      resumedBoundary.status === PENDING
    ) {
      // This must have been the last segment we were waiting on. This boundary is now complete.
      // Therefore we won't need the fallback. We early return so that we don't have to create
      // the fallback.
      resumedBoundary.status = COMPLETED;
      request.completedBoundaries.push(resumedBoundary);
      // We restore the parent componentStack. Semantically this is the same as
      // popComponentStack(task) but we do this instead because it should be slightly
      // faster
      return;

View on GitHub (pinned to eafeac097b)

Solutions

  1. Make the resumed render produce the identical tree: same keys, same order, same conditionals as the prerender pass
  2. Move nondeterministic or environment-dependent output behind boundaries that stay postponed, or gate it to the client
  3. Regenerate the prerendered payload with the same code and config as the resume runtime
  4. Accept the fallback: ensure a hydration-capable client bundle exists so React can client-render the mismatched boundary

Example fix

// before
{items.map((item) => <Row key={Math.random()} item={item} />)}
// keys differ between prerender and resume -> slots never matched

// after
{items.map((item) => <Row key={item.id} item={item} />)}
// stable keys match the recorded slots on resume
Defensive patterns

Strategy: fallback

Try / catch

// Do not try/catch: React converts this error into a client-render fallback for the boundary.
// Observe it through the error hooks of your render/resume API instead:
//   onError(err) { log.warn('resume mismatch -> client rendering fallback:', err.message); }

Prevention

When it happens

Trigger: Resuming a postponed Suspense boundary whose children render differently than during prerender: different keys, reordered siblings, flipped conditionals, or environment-dependent output between the two passes.

Common situations: Prerendering at build time and resuming in a runtime with different env vars, feature flags, locale, timezone, or random/time-based output; a stale prerendered payload deployed with newer app code.

Related errors


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