next.js
3421858b - Node.js streams: First pass (#90500)

Commit
119 days ago
Node.js streams: First pass (#90500) ### What? Note: This is only the first step. There's more follow-up PRs to implement the transforms as Node.js streams. Introduces a compile-time switchable Node.js streams layer for the app rendering pipeline, gated behind `__NEXT_USE_NODE_STREAMS`. When enabled, React's `renderToPipeableStream` / `resumeToPipeableStream` APIs produce native Node.js `Readable` streams instead of web `ReadableStream`s, eliminating repeated conversions between the two stream types during rendering. ### Why? The current rendering pipeline uses web `ReadableStream` everywhere to be consistent between Node.js/Edge, but the Node.js server ultimately pipes responses to Node `http.ServerResponse`. We found ReadableStream to be much slower than using Node.js streams directly, especially under load. By using Node.js streams natively throughout the pipeline, we can avoid this cost. ### How? - **`AnyStream` type** (`ReadableStream<Uint8Array> | Readable`): Introduced as the universal stream type across the rendering pipeline, replacing hardcoded `ReadableStream<Uint8Array>` in all stream-carrying signatures (`app-render.tsx`, `render-result.ts`, `flight-render-result.ts`, `hot-reloader-types.ts`, etc.). - **`stream-ops.ts` compile-time switcher**: Changed from a simple re-export of `stream-ops.web.ts` to a conditional `require()` that loads either `stream-ops.node.ts` or `stream-ops.web.ts` based on `process.env.__NEXT_USE_NODE_STREAMS`. Both modules export the same type surface so no casts are needed. - **`stream-ops.node.ts` (new)**: Node.js stream implementation that uses `renderToPipeableStream`, `resumeToPipeableStream`, and `PassThrough` for native pipeable rendering. Continue functions bridge to existing web transforms via `Readable.fromWeb()` / `Readable.toWeb()` at the boundary. - **`stream-ops.web.ts`**: Updated to wrap continue/utility functions instead of re-exporting them directly, so both modules have the same shaped exports. Added `teeStream`, `streamToUint8Array`, `processPrelude` wrappers. - **`debug-channel-server.ts` switcher**: Same compile-time pattern — loads `debug-channel-server.node.ts` (PassThrough-based) or `debug-channel-server.web.ts` (WritableStream-based). - **`pipe-readable.ts`**: Added `pipeNodeReadableToNodeResponse()` that pipes a Node `Readable` directly to `ServerResponse` with backpressure handling, without converting to web streams first. - **`render-result.ts`**: Accepts `Readable` as a response type. When a Node `Readable` is the response, `pipeToNodeResponse()` uses the new native pipe path. `readable` and `pipeable` getters convert via `Readable.toWeb()` only when needed. - **`app-render.tsx`**: Replaced `ReadableStream<Uint8Array>` with `AnyStream` throughout. Replaced `.tee()` calls with `teeStream()`. Updated `accumulateStreamChunks` to handle both stream types with a shared `accumulateChunk` helper. Removed `nodeStreamFromReadableStream` utility (no longer needed). - **`instant-validation.tsx`**: Changed from directly importing `renderToReadableStream` from `react-server-dom-webpack/server` to using the `renderToFlightStream` function from `stream-ops`, so validation uses the same stream backend as the main render. - **`use-flight-response.tsx`**: Updated debug stream handling to accept both `Readable` and `ReadableStream`, converting as needed.
Author
Parents
Loading