feat: use fast-webstreams for server rendering#89686
feat: use fast-webstreams for server rendering#89686feedthejim wants to merge 5 commits intocanaryfrom
Conversation
Failing test suitesCommit: 6e32119 | About building and testing Next.js
Expand output● eslint-config-next › should match expected resolved configuration
Expand output● server-side dev errors › should show server-side error for gsp page correctly ● server-side dev errors › should show server-side error for gssp page correctly ● server-side dev errors › should show server-side error for dynamic gssp page correctly ● server-side dev errors › should show server-side error for api route correctly ● server-side dev errors › should show server-side error for dynamic api route correctly
Expand output● app-dir - server source maps › handles invalid sourcemaps gracefully
Expand output● next-server-nft › with output:standalone › should not trace too many files in next-server.js.nft.json
Expand output● instant-navigation-testing-api › renders static shell on MPA navigation via plain anchor ● instant-navigation-testing-api › reload followed by MPA navigation, both block dynamic data ● instant-navigation-testing-api › successive MPA navigations within instant scope
Expand output● ppr-root-param-fallback › should have use-cache content in fallback shells for all pregenerated locales
Expand output● searchparams-reuse-loading › should re-use the prefetched loading state when navigating to a new searchParam value
Expand output● PPR - partial hydration › Static shell, no streaming metadata › should produce a valid HTML document ● PPR - partial hydration › Static shell, streaming metadata › should hydrate the shell without waiting for slow suspense boundaries ● PPR - partial hydration › Static shell, streaming metadata › should produce a valid HTML document ● PPR - partial hydration › Static shell, streaming metadata › should display the shell without JS ● PPR - partial hydration › No static shell, no streaming metadata › should produce a valid HTML document ● PPR - partial hydration › No static shell, streaming metadata › should produce a valid HTML document
Expand output● filesystem-caching with cache enabled › should cache or not cache loaders ● filesystem-caching with cache enabled › should cache or not cache loaders ● filesystem-caching with cache enabled › should cache or not cache loaders ● filesystem-caching with cache enabled › should allow to change files while stopped (RSC change) ● filesystem-caching with cache enabled › should allow to change files while stopped (RSC change) ● filesystem-caching with cache enabled › should allow to change files while stopped (RSC change) ● filesystem-caching with cache enabled › should allow to change files while stopped (RCC change) ● filesystem-caching with cache enabled › should allow to change files while stopped (RCC change) ● filesystem-caching with cache enabled › should allow to change files while stopped (RCC change) ● filesystem-caching with cache enabled › should allow to change files while stopped (Pages change) ● filesystem-caching with cache enabled › should allow to change files while stopped (Pages change) ● filesystem-caching with cache enabled › should allow to change files while stopped (Pages change) ● filesystem-caching with cache enabled › should allow to change files while stopped (rename app page) ● filesystem-caching with cache enabled › should allow to change files while stopped (rename app page) ● filesystem-caching with cache enabled › should allow to change files while stopped (rename app page) ● filesystem-caching with cache enabled › should allow to change files while stopped (next config change) ● filesystem-caching with cache enabled › should allow to change files while stopped (next config change) ● filesystem-caching with cache enabled › should allow to change files while stopped (next config change) ● filesystem-caching with cache enabled › should allow to change files while stopped (env var change) ● filesystem-caching with cache enabled › should allow to change files while stopped (env var change) ● filesystem-caching with cache enabled › should allow to change files while stopped (env var change) ● filesystem-caching with cache enabled › should allow to change files while stopped (RSC change, RCC change, Pages change, rename app page, next config change, env var change) ● filesystem-caching with cache enabled › should allow to change files while stopped (RSC change, RCC change, Pages change, rename app page, next config change, env var change) ● filesystem-caching with cache enabled › should allow to change files while stopped (RSC change, RCC change, Pages change, rename app page, next config change, env var change) ● Test suite failed to run
Expand output● nx-handling › should work for pages page ● nx-handling › should work for pages API ● nx-handling › should work with app page ● nx-handling › should work with app route ● Test suite failed to run
Expand output● opentelemetry › root context › app router › should handle RSC with fetch ● opentelemetry › root context › app router › should handle error in RSC ● opentelemetry › root context › app router › should handle error inside Suspense boundary ● opentelemetry › root context › pages › should handle getServerSideProps returning notFound ● opentelemetry › incoming context propagation › app router › should handle RSC with fetch ● opentelemetry › incoming context propagation › app router › should handle error in RSC ● opentelemetry › incoming context propagation › app router › should handle error inside Suspense boundary ● opentelemetry › incoming context propagation › pages › should handle getServerSideProps returning notFound ● opentelemetry with disabled fetch tracing › root context › app router with disabled fetch › should handle RSC with disabled fetch ● opentelemetry with custom server › should set attributes correctly on handleRequest span
Expand output● nx-handling › should work for pages page ● nx-handling › should work for pages API ● nx-handling › should work with app page ● nx-handling › should work with app route ● Test suite failed to run
Expand output● hello-world › should allow creating Spans during prerendering at runtime - inside a Cache Components ● hello-world › should allow creating Spans during resuming a fallback - inside a Cache Component
Expand output● nx-handling › should work for pages page ● nx-handling › should work for pages API ● nx-handling › should work with app page ● nx-handling › should work with app route ● Test suite failed to run |
|
dont have time to dig into this |
8e5f4d8 to
31ac09e
Compare
Replace global WebStream constructors (ReadableStream, WritableStream, TransformStream) with faster Node.js-backed implementations from experimental-fast-webstreams. The patching happens synchronously in node-environment-baseline.ts before any rendering code loads. Includes vendored fixes: - fix(pipe-to): drain buffered flush chunks before shutdown in specPipeTo - perf(pipe-to): batch sync reads to reduce microtask yields Benchmark (force-dynamic page, 4KB response): Single client: 611 -> 1,011 req/s (+65% vs native WebStreams) Under load: 1,167 -> 1,128 req/s (on par, -22% p99 latency)
31ac09e to
4a72d43
Compare
Stats from current PR🔴 3 regressions, 1 improvement
📊 All Metrics📖 Metrics GlossaryDev Server Metrics:
Build Metrics:
Change Thresholds:
⚡ Dev Server
📦 Dev Server (Webpack) (Legacy)📦 Dev Server (Webpack)
⚡ Production Builds
📦 Production Builds (Webpack) (Legacy)📦 Production Builds (Webpack)
📦 Bundle SizesBundle Sizes⚡ TurbopackClient Main Bundles: **399 kB** → **399 kB** ✅ -3 B80 files with content-based hashes (individual files not comparable between builds) Server Middleware
Build DetailsBuild Manifests
📦 WebpackClient Main Bundles
Polyfills
Pages
Server Edge SSR
Middleware
Build DetailsBuild Manifests
Build Cache
🔄 Shared (bundler-independent)Runtimes
📝 Changed Files (3 files)Files with changes:
View diffspages-api-tu..time.prod.jsDiff too large to display pages-turbo...time.prod.jsDiff too large to display server.runtime.prod.jsDiff too large to display 📎 Tarball URL |
Benchmark on Node.js v25.7.0I tested this on Node.js v25.7.0 (the version Next.js uses in production with Setup
Results
AnalysisOn Node.js v25, native WhatWG streams have improved significantly compared to v22. The fast-webstreams interop layer (which wraps Node.js streams behind the WhatWG API) now adds overhead for complex workloads:
RecommendationFor Node.js v25+, native Node.js streams (PRs #91580, #91583, #89566) are the better path. They bypass the WhatWG API entirely rather than trying to make it faster, avoiding both the native WhatWG overhead and the fast-webstreams interop overhead. The node-streams rewrite (#89566) showed +42-46% throughput even on v22 — native piping avoids the per-chunk Promise overhead that both native WhatWG and fast-webstreams still incur. |

Summary
Replace global WebStream constructors (
ReadableStream,WritableStream,TransformStream) with faster Node.js-backed implementations fromexperimental-fast-webstreams. The patching happens synchronously innode-environment-baseline.tsbefore any rendering code loads.This is a zero-application-code-change approach: just
patchGlobalWebStreams()replaces the globals, and all existing WebStream-based code in Next.js automatically uses the faster implementations.What changed
experimental-fast-webstreams@0.0.5as devDependency with a pnpm patch for bug fixesncc_fast_webstreamstask intaskfile.jsto bundle the ESM package into CJS atdist/compiled/fast-webstreams/patch.jspatchGlobalWebStreams()call innode-environment-baseline.ts(runs before any other server code)Vendored fixes (upstream PR: vercel-labs/fast-webstreams#1)
Bug fix:
specPipeTodropped chunks enqueued duringTransformStream.flush(). Thereader.closedpromise resolved before the pump drained buffered data, causing premature shutdown. Fix: move close-shutdown fromreader.closedhandler to pump's done-detection.Perf optimization: Batch sync reads in
specPipeTo. Instead of yielding viaqueueMicrotaskafter every chunk, drain all buffered chunks in one loop before yielding.Benchmark: Next.js force-dynamic page (4KB response)
Single client (c=1, 10s)
Under load (c=100, 10s)
Test plan
pnpm --filter=next buildsucceedsrequire('next/dist/compiled/fast-webstreams/patch.js')exportspatchGlobalWebStreams