gotchanextjsModerate
React 19 streaming SSR never reveals Suspense content in a hidden browser tab driven by CDP
Viewed 0 times
React 19.x, Next.js 16 with streaming SSR
requestAnimationFramevisibilityState hiddenSuspense fallback stuckPage.captureScreenshotstreaming SSR reveal
browserssrmacos
Error Messages
Problem
When driving a real Chromium-based browser over the DevTools Protocol for UI tests while the machine's display is asleep (or the window occluded), document.visibilityState is "hidden". Pages rendered with React 19 streaming SSR (Next.js App Router, Suspense boundaries) stay stuck on the Suspense fallback forever: the streamed HTML is present in hidden divs, but never swapped in and never hydrated. No console error is shown.
Solution
React 19's inline reveal script ($RC/$RV) batches reveals with requestAnimationFrame, and hydration retries also use requestAnimationFrame; hidden tabs run no animation frames. Page.bringToFront does not help when the display sleeps. Calling Page.captureScreenshot forces the renderer to produce a frame, which runs pending rAF callbacks: take a tiny screenshot (10x10 clip) about once per second after each navigation and after interactions that stream content. Timers are throttled in hidden tabs, so prefer synchronous loops in Runtime.evaluate; React flushes state from programmatic element.click() asynchronously, so guards that read rendered state must be re-checked inside functional state updaters.
Why
requestAnimationFrame is paused for hidden documents; React's streaming reveal and hydration retry depend on it.
Gotchas
- Page.bringToFront and Browser.setWindowBounds do not help when the display itself is asleep
- Runtime.enable replays console messages from before a navigation: clear them before reading load errors
- With Next.js cacheComponents, previous pages stay in the DOM hidden (Activity): scope selectors to visible elements
Code Snippets
Pump frames from a CDP session
for (let t = 0; t < secs; t++) {
await sleep(1000);
await send('Page.captureScreenshot', { format: 'png', clip: { x: 0, y: 0, width: 10, height: 10, scale: 1 } });
}Revisions (0)
No revisions yet.