-
Notifications
You must be signed in to change notification settings - Fork 50.2k
Description
tl;dr: I'd like to know how much enthusiasm there is on the core React team for accepting a PR with the ability to render markup to a stream. I don't need a guarantee, of course, but I'd like to get an sense if it's something the React team might do before spending a ton of time working on it.
Background
Currently, server rendering is accomplished by ReactDOMServer.renderToString or ReactDOMServer.renderToStaticMarkup, which are synchronous functions that return a string.
The server render methods can be somewhat slow. Large (~500K) web pages can easily take over 100 or 200ms to render, depending on the server. Also, since node is single threaded and single-core performance has not been improving rapidly, this is not just a problem that Moore's Law will eventually solve. The slowness in turn causes two problems in production websites with large (or somewhat large) HTML pages:
- First, because the methods return a string, they completely render the entire component before you can return even the first byte to the browser, meaning that the browser is waiting idle while the render method is called.
- Second, because the methods are synchronous and node is single threaded, the server is unresponsive during rendering, which means that a large page render can easily starve out renders of smaller pages (which may only take a few ms). An asynchronous render method would let large page renders yield CPU to small pages, improving average response time.
Proposed Solution
The proposal is to add two methods to ReactDOMServer, both of which return a node Readable Stream.
Stream renderToStream(ReactElement element)
Stream renderToStaticMarkupStream(ReactElement element)In terms of behavior, these methods would return a Readable Stream that outputs the same markup as renderToString and renderToStaticMarkup, respectively.
These methods could solve both problems above. A stream can begin emitting content immediately to send to the browser, keeping TTFB constant as components get larger. Streams are also inherently asynchronous, which allows large pages to not starve out smaller ones.
A few months back I forked react into a library called react-dom-stream to do this exact thing, and it's been fairly well received (1,100+ stars on GitHub, though modest actual downloads on npm). There's also a wishlist bug thread on this repo about it. However, maintaining a fork is a less than desirable position, and I wonder if I can get it integrated in to the core code base.
My implementation in react-dom-stream is not ideal; I ended up copying basically every implementation of mountComponent and making the copied versions asynchronous. The benefit of this approach was that it didn't affect renderToString performance, but the obvious drawback was that any code changes or bug fixes would have to be performed in both forks of the code, which is not ideal.
Recently, I figured out a way to share code in a better way between renderToString and renderToStream, but the result was that renderToString slowed down relatively significantly (+30%, plus or minus). I'm trying to figure out how if I can tune it more carefully, but it's not looking great yet.
Questions
So I think the questions I'd like to ask to the team are:
- Assuming it were perfectly implemented with no performance degradation of the current code, is this even something you are willing to accept a PR for this feature?
- If you were to accept this, how important to you is it that the string and stream versions of the code follow the same code path?
- Are you willing to accept any degradation in performance of the string version of server rendering in order to get stream rendering that has constant TTFB and keep a single code path?
Thanks for your time, and thanks for all the great work you do on react!
(Tagging @spicyj, @gaearon, and @sebmarkbage, as I was instructed to do on the react IRC channel.)