Repository: facebook/react
Pull request: [Fizz] Only compute component stacks in DEV and prerenders

If you have a lot of intentional throws (or postpones) from client-only rendering then computing the stack is too much.

Changed files:
filename: packages/react-server/src/ReactFizzServer.js
patch: @@ -769,8 +769,17 @@ export type PostponeInfo = ThrownInfo;
 // during prerender in Prod. The reason for this is that the stack is useful for prerender where the timeliness
 // of the request is less critical than the observability of the execution. For renders and resumes however we
 // prioritize speed of the request.
-function getThrownInfo(node: null | ComponentStackNode): ThrownInfo {
-  if (node) {
+function getThrownInfo(
+  request: Request,
+  node: null | ComponentStackNode,
+): ThrownInfo {
+  if (
+    node &&
+    // Always produce a stack in dev
+    (__DEV__ ||
+      // Produce a stack in prod if we're in a prerender
+      request.trackedPostpones !== null)
+  ) {
     return {
       componentStack: getStackFromNode(node),
     };
@@ -968,7 +977,7 @@ function renderSuspenseBoundary(
   } catch (error: mixed) {
     contentRootSegment.status = ERRORED;
     newBoundary.status = CLIENT_RENDERED;
-    const thrownInfo = getThrownInfo(task.componentStack);
+    const thrownInfo = getThrownInfo(request, task.componentStack);
     let errorDigest;
     if (
       enablePostpone &&
@@ -1117,7 +1126,7 @@ function replaySuspenseBoundary(
     }
   } catch (error: mixed) {
     resumedBoundary.status = CLIENT_RENDERED;
-    const thrownInfo = getThrownInfo(task.componentStack);
+    const thrownInfo = getThrownInfo(request, task.componentStack);
     let errorDigest;
     if (
       enablePostpone &&
@@ -2088,7 +2097,7 @@ function replayElement(
         // in the original prerender. What's unable to complete is the child
         // replay nodes which might be Suspense boundaries which are able to
         // absorb the error and we can still continue with siblings.
-        const thrownInfo = getThrownInfo(task.componentStack);
+        const thrownInfo = getThrownInfo(request, task.componentStack);
         erroredReplay(
           request,
           task.blockedBoundary,
@@ -2424,7 +2433,7 @@ function replayFragment(
       // replay nodes which might be Suspense boundaries which are able to
       // absorb the error and we can still continue with siblings.
       // This is an error, stash the component stack if it is null.
-      const thrownInfo = getThrownInfo(task.componentStack);
+      const thrownInfo = getThrownInfo(request, task.componentStack);
       erroredReplay(
         request,
         task.blockedBoundary,
@@ -2888,7 +2897,7 @@ function renderNode(
           const trackedPostpones = request.trackedPostpones;
 
           const postponeInstance: Postpone = (x: any);
-          const thrownInfo = getThrownInfo(task.componentStack);
+          const thrownInfo = getThrownInfo(request, task.componentStack);
           const postponedSegment = injectPostponedHole(
             request,
             ((task: any): RenderTask), // We don't use ReplayTasks in prerenders.
@@ -3168,7 +3177,7 @@ function abortTask(task: Task, request: Request, error: mixed): void {
       boundary.status = CLIENT_RENDERED;
       // We construct an errorInfo from the boundary's componentStack so the error in dev will indicate which
       // boundary the message is referring to
-      const errorInfo = getThrownInfo(task.componentStack);
+      const errorInfo = getThrownInfo(request, task.componentStack);
       const errorDigest = logRecoverableError(request, error, errorInfo);
       let errorMessage = error;
       if (__DEV__) {
@@ -3470,15 +3479,15 @@ function retryRenderTask(
         task.abortSet.delete(task);
         const postponeInstance: Postpone = (x: any);
 
-        const postponeInfo = getThrownInfo(task.componentStack);
+        const postponeInfo = getThrownInfo(request, task.componentStack);
         logPostpone(request, postponeInstance.message, postponeInfo);
         trackPostpone(request, trackedPostpones, task, segment);
         finishedTask(request, task.blockedBoundary, segment);
         return;
       }
     }
 
-    const errorInfo = getThrownInfo(task.componentStack);
+    const errorInfo = getThrownInfo(request, task.componentStack);
     task.abortSet.delete(task);
     segment.status = ERRORED;
     erroredTask(request, task.blockedBoundary, x, errorInfo);
@@ -3562,7 +3571,7 @@ function retryReplayTask(request: Request, task: ReplayTask): void {
     }
     task.replay.pendingTasks--;
     task.abortSet.delete(task);
-    const errorInfo = getThrownInfo(task.componentStack);
+    const errorInfo = getThrownInfo(request, task.componentStack);
     erroredReplay(
       request,
       task.blockedBoundary,