{"record":{"id":"88f119d8169b2005","repo":"facebook/react","slug":"there-should-always-be-an-offscreen-fiber-child-in","errorCode":null,"errorMessage":"There should always be an Offscreen Fiber child in a hydrated Suspense boundary.","messagePattern":"There should always be an Offscreen Fiber child in a hydrated Suspense boundary\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/react-devtools-shared/src/backend/fiber/renderer.js","lineNumber":3299,"sourceCode":"      if (fiber.tag === SuspenseComponent || fiber.tag === HostRoot) {\n        newSuspenseNode = createSuspenseNode(newInstance);\n        // Measure this Suspense node. In general we shouldn't do this until we have\n        // inserted the new children but since we know this is a FiberInstance we'll\n        // just use the Fiber anyway.\n        // Fallbacks get attributed to the parent so we only measure if we're\n        // showing primary content.\n        if (fiber.tag === SuspenseComponent) {\n          if (OffscreenComponent === -1) {\n            const isTimedOut = fiber.memoizedState !== null;\n            if (!isTimedOut) {\n              newSuspenseNode.rects = measureInstance(newInstance);\n            }\n          } else {\n            const hydrated = isFiberHydrated(fiber);\n            if (hydrated) {\n              const contentFiber = fiber.child;\n              if (contentFiber === null) {\n                throw new Error(\n                  'There should always be an Offscreen Fiber child in a hydrated Suspense boundary.',\n                );\n              }\n            } else {\n              // This Suspense Fiber is still dehydrated. It won't have any children\n              // until hydration.\n            }\n            const isTimedOut = fiber.memoizedState !== null;\n            if (!isTimedOut) {\n              newSuspenseNode.rects = measureInstance(newInstance);\n            }\n          }\n        } else {\n          newSuspenseNode.rects = measureInstance(newInstance);\n        }\n        recordSuspenseMount(newSuspenseNode, reconcilingParentSuspenseNode);\n      }\n      insertChild(newInstance);","sourceCodeStart":3281,"sourceCodeEnd":3317,"githubUrl":"https://github.com/facebook/react/blob/eafeac097ba51e1eab809c07102126bd5f8e5425/packages/react-devtools-shared/src/backend/fiber/renderer.js#L3281-L3317","documentation":"DevTools backend invariant hit while mounting the mirrored tree: on modern React (where OffscreenComponent is a known tag), a SuspenseComponent fiber that is already hydrated must have a child fiber - the Offscreen wrapper that React puts around suspense content. A null fiber.child on a hydrated boundary breaks the traversal's structural assumption about React 19+ trees.","triggerScenarios":"DevTools mirroring an SSR-hydrated Suspense boundary whose content fiber is missing; DevTools attached during hydration; a React build that omits the Offscreen wrapper (older or non-standard renderer) paired with a DevTools backend that expects it.","commonSituations":"React 19 / canary with an outdated DevTools extension; hydration of Suspense boundaries (including Activity/Offscreen usage) while the Components tree is open; third-party renderers reusing react-devtools-shared with different child layouts.","solutions":["Update React DevTools to the version aligned with your React release channel","Let hydration finish before opening DevTools, or reload with DevTools already attached so the tree is captured post-hydration","Report the boundary structure (Suspense nesting, Activity usage, hydration mode) as a repro to facebook/react","If you control the build, verify only one copy of react-devtools-shared is loaded"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  backend.handleCommitFiberRoot(renderer, root);\n} catch (e) {\n  if (/Offscreen Fiber child in a hydrated Suspense boundary/.test(e.message)) {\n    reportDevToolsBug(e); // hydration-shape invariant: report, then reload backend\n  } else throw e;\n}","preventionTips":["Match DevTools and React versions, especially across React 19 Offscreen/Activity changes","Let hydration finish (or attach DevTools after load) before inspecting hydrated Suspense trees","Report hydration + Suspense tree shapes that trigger it - the invariant encodes a React contract","Reload the page to rebuild the mirror after any DevTools invariant error"],"tags":["react-devtools","invariant","suspense","hydration","offscreen","internal"],"backgroundTag":"suspense-boundary-invariant-violation","analyzedSha":"eafeac097ba51e1eab809c07102126bd5f8e5425","analyzedAt":"2026-08-21T22:01:08.818Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}