[GH-ISSUE #1011] Add waitForAllReady forkpoint to renderToNodeFizzStream for prerenderToStream #225

Open
opened 2026-05-06 12:38:20 +02:00 by BreizhHardware · 0 comments

Originally created by @github-actions[bot] on GitHub (May 2, 2026).
Original GitHub issue: https://github.com/cloudflare/vinext/issues/1011

Upstream change

Next.js re-landed a change to renderToNodeFizzStream (Node runtime) that adds a third options argument with a waitForAllReady flag. When set, the pipeable React stream is connected to the output PassThrough only after onAllReady fires, instead of on onShellReady. This effectively gives the prerender path its own forkpoint: the static prerender can wait for the full tree before emitting bytes, while normal SSR keeps streaming as soon as the shell is ready.

The web variant (stream-ops.web.ts) accepts the same option for signature parity but does not change behavior yet.

Relevant diff (packages/next/src/server/app-render/stream-ops.node.ts):

export async function renderToNodeFizzStream(
  element: React.ReactElement,
  streamOptions: any,
  options?: { waitForAllReady?: boolean }
): Promise<FizzStreamResult> {
  const pt = new PassThrough()
  const shellReady = new DetachedPromise<void>()
  const allReady = new DetachedPromise<void>()
  const deferPipe = options?.waitForAllReady === true

  const pipeable = getTracer().trace(AppRenderSpan.renderToReadableStream, () =>
    renderToPipeableStream(element, {
      ...streamOptions,
      onShellReady() {
        streamOptions?.onShellReady?.()
        if (!deferPipe) {
          pipeable.pipe(pt)
        }
        shellReady.resolve()
      },
      onAllReady() {
        streamOptions?.onAllReady?.()
        if (deferPipe) {
          pipeable.pipe(pt)
        }
        allReady.resolve()
      },
      ...
    })
  )
}

app-render.tsx is also updated to thread this option through prerenderToStream so static prerenders can opt into the deferred pipe.

Why this matters for vinext

vinext targets Cloudflare Workers, so the web renderer (renderToReadableStream) is the primary path and this change is signature-only there. However:

  • The Node dev server and any non-Workers prod target use the Node Fizz path. If/when vinext exposes a static prerender or generateStaticParams build step that needs the full tree before flushing, we should mirror this waitForAllReady forkpoint instead of relying on onShellReady.
  • For parity we should make sure our prerender path on the web runtime correctly waits for allReady when producing static HTML for Cloudflare (e.g. ISR cache writes), since the static output should not be a shell + suspended stream.

This is mainly a tracking item: confirm vinext's prerender code path equivalents pipe only after allReady for static output, both on the Node dev path and the web/Workers prerender path used for ISR.

Suggested work

  • Audit vinext's static prerender / ISR generation code paths (App Router) and ensure they wait for the full React tree before serializing HTML to the cache.
  • If we ever add a Node-runtime production server, port the waitForAllReady option to the equivalent stream helper.
  • Add a regression test that an ISR-cached page does not contain Suspense boundary fallback markers when the underlying data resolves before the cache write.
Originally created by @github-actions[bot] on GitHub (May 2, 2026). Original GitHub issue: https://github.com/cloudflare/vinext/issues/1011 ## Upstream change Next.js re-landed a change to `renderToNodeFizzStream` (Node runtime) that adds a third `options` argument with a `waitForAllReady` flag. When set, the pipeable React stream is connected to the output `PassThrough` only after `onAllReady` fires, instead of on `onShellReady`. This effectively gives the prerender path its own forkpoint: the static prerender can wait for the full tree before emitting bytes, while normal SSR keeps streaming as soon as the shell is ready. The web variant (`stream-ops.web.ts`) accepts the same option for signature parity but does not change behavior yet. - Commit: https://github.com/vercel/next.js/commit/b2491e30cbf03d8a6dd75ebf8baeb8df5fb5752f - PR: https://github.com/vercel/next.js/pull/93376 (re-land of #92845) Relevant diff (`packages/next/src/server/app-render/stream-ops.node.ts`): ```ts export async function renderToNodeFizzStream( element: React.ReactElement, streamOptions: any, options?: { waitForAllReady?: boolean } ): Promise<FizzStreamResult> { const pt = new PassThrough() const shellReady = new DetachedPromise<void>() const allReady = new DetachedPromise<void>() const deferPipe = options?.waitForAllReady === true const pipeable = getTracer().trace(AppRenderSpan.renderToReadableStream, () => renderToPipeableStream(element, { ...streamOptions, onShellReady() { streamOptions?.onShellReady?.() if (!deferPipe) { pipeable.pipe(pt) } shellReady.resolve() }, onAllReady() { streamOptions?.onAllReady?.() if (deferPipe) { pipeable.pipe(pt) } allReady.resolve() }, ... }) ) } ``` `app-render.tsx` is also updated to thread this option through `prerenderToStream` so static prerenders can opt into the deferred pipe. ## Why this matters for vinext vinext targets Cloudflare Workers, so the web renderer (`renderToReadableStream`) is the primary path and this change is signature-only there. However: - The Node dev server and any non-Workers prod target use the Node Fizz path. If/when vinext exposes a static prerender or `generateStaticParams` build step that needs the full tree before flushing, we should mirror this `waitForAllReady` forkpoint instead of relying on `onShellReady`. - For parity we should make sure our prerender path on the web runtime correctly waits for `allReady` when producing static HTML for Cloudflare (e.g. ISR cache writes), since the static output should not be a shell + suspended stream. This is mainly a tracking item: confirm vinext's prerender code path equivalents pipe only after `allReady` for static output, both on the Node dev path and the web/Workers prerender path used for ISR. ## Suggested work - Audit vinext's static prerender / ISR generation code paths (App Router) and ensure they wait for the full React tree before serializing HTML to the cache. - If we ever add a Node-runtime production server, port the `waitForAllReady` option to the equivalent stream helper. - Add a regression test that an ISR-cached page does not contain Suspense boundary fallback markers when the underlying data resolves before the cache write.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#225
No description provided.