AI & Agent Dev Bug Sandbox logo
AI & Agent Dev Bug Sandbox
Back to Radar

Streamed Suspense Boundaries Never Reveal In Hidden Documents Due To RAF Starvation In Vendored React-Dom

Next.js pages that load in hidden tabs, occluded windows, or iOS standalone PWAs may permanently show fallback content even after the streamed content has fully arrived. The vendored react-dom's streaming reveal scheduler only schedules the first reveal via requestAnimationFrame when $RT is undefined; hidden documents never fire rAF, causing the $RB queue to remain parked and no hydration to occur.

highConfidence 95%Next.jsAffected V16.2.6Affected V16.2.7Affected V16.2.8Affected V16.2.9Affected V16.2.10Affected V16.3.0-Canary.79

Origin Analysis

The react-dom Fizz instruction set ($RC/$RV) batches streamed Suspense reveals. The $RC function checks if $RT is a number; if not, it schedules $RV via requestAnimationFrame. $RT is only set inside $RV, so for a document that has never painted (hidden from birth), $RT remains undefined, and the first reveal batch is scheduled exclusively via rAF. Browsers do not fire rAF in hidden/occluded documents, so the reveal never runs. Subsequent $RC calls only push to $RB without rescheduling, and the $~ marker disables client-side recovery, leaving the fallback live indefinitely.
1. Create a Next.js App Router page with a loading.js fallback and an async page that resolves after 1.5s (force-dynamic). 2. Build and start the app. 3. Open the page as a document load in a hidden tab (e.g., cmd/ctrl-click a link or switch tabs before load begins) and keep the document hidden past the content delay. 4. Without focusing the tab, attach DevTools and check: document.visibilityState === 'hidden', document.readyState === 'complete', window.$RB.length === 2, typeof window.$RT === 'undefined', the S:0 content segment exists in DOM but is hidden, and the fallback is still visible. 5. The page stays in this state indefinitely until the tab is focused (which fires the parked rAF) or $RV is manually invoked.

Fixing Code Block

Edge Case Audit

This is a user-land workaround, not a replacement for a fixed Next.js/React version. It requires the inline script to execute before the streamed $RC scripts; if a Content Security Policy blocks inline scripts, or if the layout structure changes so the script runs after the first $RC, the workaround fails. It also does not cover the rare case where the document starts visible, schedules the first reveal via rAF, and then becomes hidden before the rAF fires; in that scenario the rAF may still be starved. Additionally, setting $RT based on hidden state may slightly change reveal timing for hidden documents (reveal occurs after a setTimeout delay instead of on first paint). Rollback is straightforward: remove the script and upgrade to a Next.js version with the backported React fix. Avoid applying this if you rely on exact first-paint reveal synchronization for visible documents.

Ecosystem Topology