From 4a95af8084a681e3f26a847331ee5f2f8de52a42 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Wed, 19 Aug 2026 00:32:39 -0700 Subject: [PATCH 1/7] Fix use cache prerender signal retention (#97476) ## Summary After a fallback-shell cache prerender completes, snapshot whether its timeout fired and then abort the existing timeout controller when it participates in an `AbortSignal.any()` composite. This triggers the composite so React removes its abort listener; no additional controller or signal is needed. Cache prerenders without a dynamic-access source keep their existing direct timeout signal. Node retains non-empty composite abort signals while they have abort listeners. React attaches such a listener during `prerender()` and removes it when the signal aborts, so aborting the already-owned timeout source releases the successful render. Snapshotting `didTimeout` first keeps cleanup aborts distinct from real timeouts. This preserves the early aborted-prerender guard from #96426, which prevents a cache fill that starts after its outer prerender aborts from caching an empty React stream. Fixes #97363 Related #97464 Alternative to #97391 ## Verification - `pnpm --filter=next build` - `pnpm test-start-turbo test/e2e/app-dir/use-cache-after-uncached-io/use-cache-after-uncached-io.test.ts` - `pnpm test-start-turbo test/e2e/app-dir/use-cache-hanging/use-cache-hanging.test.ts` - Actual vendored React `prerender()` GC probe on Node 20.19.5 and 22.20.0: valid preludes, no cleanup errors, and 0/100 composite signals retained while their source controllers remained reachable. --- packages/next/src/server/use-cache/use-cache-wrapper.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/next/src/server/use-cache/use-cache-wrapper.ts b/packages/next/src/server/use-cache/use-cache-wrapper.ts index 1f8f423ce227..63f482e681c3 100644 --- a/packages/next/src/server/use-cache/use-cache-wrapper.ts +++ b/packages/next/src/server/use-cache/use-cache-wrapper.ts @@ -1412,8 +1412,13 @@ async function generateCacheEntryImpl( ) clearTimeout(timer) + const didTimeout = timeoutAbortController.signal.aborted + if (dynamicAccessAbortSignal) { + // Release React's listener from the composite signal. + timeoutAbortController.abort() + } - if (timeoutAbortController.signal.aborted) { + if (didTimeout) { // When the timeout is reached we always error the stream. Even for // fallback shell prerenders we don't want to return a hanging promise, // which would allow the function to become a dynamic hole. Because that From 78b11c37e6eafb92030612c08de4adb5bb5c8a28 Mon Sep 17 00:00:00 2001 From: Joseph Date: Wed, 19 Aug 2026 10:31:44 +0200 Subject: [PATCH 2/7] docs: outlining and lcp (#96942) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LCP can be affected if the LCP element is sitting within a Suspense boundary, even if its nothing but static content, if the static content is "too large". Apps should prefer, as long as possible, to not have a Suspense boundary around the LCP element. - https://react.dev/reference/react/Suspense#what-activates-a-suspense-boundary > Waiting for a large boundary’s HTML to arrive during streaming server rendering. Sending HTML takes time, so a boundary with enough content activates even when nothing in it suspends. React reveals the content as the HTML arrives. --- docs/01-app/02-guides/streaming.mdx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/01-app/02-guides/streaming.mdx b/docs/01-app/02-guides/streaming.mdx index a25bbaea0490..4f65f0a32b0e 100644 --- a/docs/01-app/02-guides/streaming.mdx +++ b/docs/01-app/02-guides/streaming.mdx @@ -36,7 +36,7 @@ When a browser requests a page, two streams work together during the initial pag ### The HTML stream -React's server renderer produces progressive HTML chunks. The static parts of your page (layouts, navigation, Suspense fallbacks) render first and are sent immediately. When an async [Server Component](/docs/app/glossary#server-component) resolves, React streams its completed HTML along with inline `