next.js
de7ce396 - Node.js streams: Fork points (#92252)

Commit
5 days ago
Node.js streams: Fork points (#92252) ### What? Implements native Node.js stream support for the RSC / HTML rendering. This first step does not include cacheComponents / static generation. Instead of converting between Node.js Readable streams and Web ReadableStreams at every boundary, the rendering pipeline now uses native Node.js `Transform`/`PassThrough`/`Readable` streams end-to-end when the useNodeStreams experimental flag is enabled. ### Why? The current rendering pipeline has a bottleneck on Web Streams. Each step adds overhead: buffering, scheduling mismatches, and unnecessary memory copies. Node.js streams are the native I/O primitive on the server. Using them directly eliminates these conversion and scheduling/promise costs. This is the foundational plumbing needed to run the full app render pipeline on native Node.js streams, which improves server-side rendering throughput. ### How? **Stream transform reimplementation** (`stream-ops.node.ts`): All web `TransformStream`-based transforms have been reimplemented as Node.js `Transform` streams: - `createBufferedTransformStream` — coalesces chunks written in the same microtask into a single `Uint8Array` - `createFlightDataInjectionTransform` — interleaves RSC flight data chunks with the HTML stream - `createHeadInsertionTransform` — inserts server-generated HTML (polyfills, scripts) before `</head>` - `createMetadataTransform` — finds and replaces the `«nxt-icon»` meta mark - `createDeferredSuffixTransform` — appends the document suffix after the first HTML chunk - `createMoveSuffixTransform` — moves `</body></html>` to the end of the stream - `createHtmlDataDplIdTransform` — inserts a `data-dpl-id` attribute on the opening `<html>` tag - `createRootLayoutValidatorTransform` — validates `<html>` and `<body>` tags are present (dev only) **`ReplayableNodeStream`** (`app-render-prerender-utils.ts`): New class that buffers all chunks from a source `Readable` and allows creating multiple independent replay streams via `createReplayStream()`. This replaces the web stream `tee()` operation for `ReactServerResult` when using Node.js streams. Uses pull-based delivery (`_read()`) to avoid AsyncLocalStorage context issues — chunks are only delivered when the consumer reads, keeping them inside the correct ALS scope during Fizz's `performWork`. Includes 392 lines of unit tests covering buffering, live replay, error propagation, multiple independent replays, subscriber cleanup, and dispose behavior. **Native rendering functions**: - `renderToNodeFlightStream` — calls React's `renderToPipeableStream` directly, pipes to a `PassThrough` - `renderToNodeFizzStream` — calls `renderToPipeableStream` for HTML rendering without the web stream wrapper - `createNodeInlinedDataStream` — wraps RSC flight data in `<script>` tags using `Buffer` operations and `isUtf8()` for encoding detection **`continueFizzStream` rewritten for Node.js**: Instead of converting to web streams and delegating to `webContinueFizzStream`, the Node.js path pipes through a chain of native `Transform` streams directly: buffer → dpl-id → metadata → deferred suffix → flight data injection → root layout validator → move suffix → head insertion. **API renames for clarity**: - `renderToFlightStream` → `renderToWebFlightStream` / `renderToNodeFlightStream` - `renderToFizzStream` → `renderToWebFizzStream` / `renderToNodeFizzStream` - `createInlinedDataStream` → `createWebInlinedDataStream` / `createNodeInlinedDataStream` - `createDebugChannel` → `createWebDebugChannel` / `createNodeDebugChannel` **`app-render.tsx` fork points**: The main render function now branches on `process.env.__NEXT_USE_NODE_STREAMS` at two key points: 1. RSC rendering — uses `renderToNodeFlightStream` + `createNodeDebugChannel` 2. HTML rendering — uses `renderToNodeFizzStream` + native `continueFizzStream` with `createNodeInlinedDataStream` All pre-render, cache components, and dev staged-render paths continue to use web streams. **CI updates** (`.github/workflows/build_and_test.yml`): The `use-node-streams` test jobs now run without `__NEXT_CACHE_COMPONENTS` / `__NEXT_EXPERIMENTAL_CACHED_NAVIGATIONS` / `__NEXT_EXPERIMENTAL_APP_NEW_SCROLL_HANDLER` to isolate the Node.js streams path.
Author
Parents
Loading