Repository navigation
Description
Link to the code that reproduces this issue
To Reproduce
npm install && npm run build(any Node version).- Start the build with Node 24.x:
node node_modules/next/dist/bin/next start -p 3100, then in another terminal runnode measure.mjs http://127.0.0.1:3100/ 7. - Stop the server, start the same build with Node 26.x, and run
measure.mjsagain.
measure.mjs fetches the page with gzip and prints medians of inline Flight scripts, response chunks, gzip bytes and request time.
Current vs. Expected behavior
Current: on Node 26, Next 16.4.5 splits the inline Flight data into one <script>self.__next_f.push(…)</script> per chunk React buffered. Each script is a separate response write and gzip flush.
| inline Flight scripts | response chunks | gzip bytes | median request | |
|---|---|---|---|---|
| next 16.4.5 · Node 24.22.2 | 3 | 4 | 10,771 | 19.2 ms |
| next 16.4.5 · Node 26.13.0 | 407 | 411 | 47,301 | 26.1 ms |
| next 16.5.0-canary.43 · Node 26.13.0 | 3 | 5 | 10,786 | 17.4 ms |
Expected: the same output on Node 26 as on Node 24.
Cause. Node.js 26.0.0 changed Readable#read(): called with no size, it returns one buffered chunk instead of everything buffered (nodejs/node#60441, semver-major; the Node docs still describe the old behaviour, see nodejs/node#67323). pullFlightData in app-render loops on dataStream.read() and wraps each result in its own script. So the number of scripts now follows the number of chunks React wrote. Long strings (over ~1.3 K characters) are their own chunks.
Seen on a real app. We compared Node 24.22.2 and 26.13.0 on the same App Router build: 5 rounds of 600 requests at concurrency 8, with a Node 24 vs Node 24 control run to measure noise. On Node 26, p95 was +28 % and p99 +35 % on average, outside the noise band in 5 of 5 rounds. Our heaviest page went from 5 to 106 inline scripts, with 67 % more gzip bytes. Changing that one call to dataStream.read(dataStream.readableLength || undefined) brought Node 26 back within noise. That is the migration hint from nodejs/node#60441.
Already fixed on canary. #100142 ("Build the inline Flight script from React's strings instead of re-encoding them", merged 2026-10-24) switches the Flight stream to object mode and batches every buffered chunk into one script. 16.5.0-canary.43 does not reproduce (row 3 above).
Request. Could that batching, or the one-line read(readableLength) change, go out in a 16.4.x patch release? Node 26 became LTS with 26.13.0 (2026-10-28), so more apps on 16.4 will hit this as they upgrade.
Provide environment information
Operating System:
Platform: linux
Arch: x64
Available memory (MB): 128534
Available CPU cores: 20
Binaries:
Node: 26.13.0
npm: 11.20.0
Relevant Packages:
next: 16.4.5 // Latest available version is detected (16.4.5).
eslint-config-next: N/A
react: 19.3.1
react-dom: 19.3.1
typescript: N/A
Next.js Config:
output: N/AWhich area(s) are affected? (Select all that apply)
Performance, Loading UI and Streaming, Runtime
Which stage(s) are affected? (Select all that apply)
next start (local), Other (Deployed)
Additional context
Node 24.x is not affected. The repro uses plain JSX, with no TypeScript or config, and the same build on both runtimes.
Activity
ruturaaj16 commentedon Nov 7, 2026
@toolmantim can I work on this issue, can you assign it to me?