-
Notifications
You must be signed in to change notification settings - Fork 49.9k
Description
Hello!
I'm really like Server Components!
In my day job I do a lot of SSR and Server Components allows make it much simpler and faster.
But when I've switched from regular renderToPipeableStream to RSC SSR I've noticed a decrease in server throughput, from 329 to 29 RPS, and an increase in response timings. I've also noticed decrease in server throughput of flight request from 329 to 104 RPS in comparison to renderToPipeableStream.
Steps To Reproduce
I've create a demo repo which illustrates the issue by benchmarking RSC SSR on React 19.1.1, Node.js 22 and Parcel 2.15.4.
For benchmarking i'm using parcel SSR implementation because it short and straightforward but the issue presents in Next.js also. Parcel approach looks similar to this:
import {renderToReadableStream} from 'react-server-dom-parcel/server.edge';
import {injectRSCPayload} from 'rsc-html-stream/server';
import {createFromReadableStream} from 'react-server-dom-parcel/client.edge';
import {renderToReadableStream as renderHTMLToReadableStream} from 'react-dom/server.edge';
async function renderHTML(root: any): Promise<ReadableStream> {
// serialize react tree to flight stream
let rscStream = renderToReadableStream(root, options);
// Use client react to render the RSC payload to HTML.
let [s1, s2] = rscStream.tee();
let data: Promise<ReactNode>;
function Content() {
// deserialize flight stream to react tree
data ??= createFromReadableStream<ReactNode>(s1);
return data;
}
// serialize flight react tree to HTML
let htmlStream = await renderHTMLToReadableStream(<Content />);
// inject JSON flight tree to html for hydration
return htmlStream.pipeThrough(injectRSCPayload(s2));
}In the benchmark server renders Stress component with 10,000 list items.
import React from 'react';
const count = 10_000;
const Stress = () => {
const out = Array(count);
for (let i = 0; i < count; i++) {
out[i] = <li key={i}>{i}</li>
}
return <ul>{out}</ul>;
}
export default Stress;The current behavior
If we try to record CPU Profile during request then we'll see that most of computing spent for JSON serialization and html rendering.
CPU Profile in production mode:
CPU-ssr-rsc-prod.cpuprofile

CPU Profile in development mode:
CPU-rsc-ssr-dev.cpuprofile

The expected behavior
I'm understand that JSON serialization is essential for RSC to work but I'm think that there is a optimization opportunities to discover:
- V8 team tells in blog article that
JSON.stringifycan run faster if it serializes only plain objects without custom prototype.toJSON() method. In theory refactoring could speed up flight serialization. - In ReactFizzServer I saw a TODO comment that suppose injecting flight data for hydration directly in Fizz Stream. It might reduce amount of serialization in comparison to current RSC SSR.