React 16 released! Talking about this event, we can mention a lot of great news (like the Fibers core architecture), but personally I am most impressed by the improvements in server rendering. I propose to analyze all this in detail and compare it with what it was before. I hope you will like the server rendering in React 16 as much as I liked it.

How SSR works in React 15
To begin, recall how server-side rendering (Server-Side Rendering, SSR) looks like in React 15. To perform SSR, a server based on a Node using Express, Hapi or Koa is usually supported and called
renderToString
to convert the root component to a string that write to the server response:
// Express import { renderToString } from "react-dom/server" import MyPage from "./MyPage" app.get("/", (req, res) => { res.write("<!DOCTYPE html><html><head><title>My Page</title></head><body>"); res.write("<div id='content'>"); res.write(renderToString(<MyPage/>)); res.write("</div></body></html>"); res.end(); });
When the client receives the answer, the client rendering subsystem, in the template code, is given the command to restore the HTML generated on the server using the
render()
method. The same method is used in applications rendering on the client without server participation:
')
import { render } from "react-dom" import MyPage from "./MyPage" render(<MyPage/>, document.getElementById("content"));
If done correctly, the client rendering system can simply use the HTML generated on the server without updating the DOM.
What does SSR look like in React 16?
React 16 Backward Compatibility
The React development team showed a clear orientation towards backward compatibility. Therefore, if your code is executed in React 15 without reports of obsolete constructs, it should simply work in React 16 without any additional effort on your part. The code above, for example, works fine in both React 15 and React 16.
If it happens that you run your application on React 16 and encounter errors, please
report them! This will help the development team.
The render () method becomes the hydrate () method.
It should be noted that when switching from React 15 to React 16, you may encounter the following warning in the browser.
Another useful warning React. The render () method is now called hydrate ()It turns out that in React 16 there are now two different methods for rendering on the client side. The
render()
method for situations where rendering is performed entirely on the client, and the
hydrate()
method for cases where the rendering on the client is based on the server rendering results. Thanks to the backward compatibility of the new version of React,
render()
will work even if you transfer to it what came from the server. However, these calls should be replaced by calls to
hydrate()
in order for the system to stop issuing warnings, and to prepare the code for React 17. With this approach, the code shown above would change like this:
import { hydrate } from "react-dom" import MyPage from "./MyPage" hydrate(<MyPage/>, document.getElementById("content"))
React 16 can work with arrays, strings and numbers
In React 15, the component's
render()
method should always return a single React element. However, in React 16, client-side rendering allows components to also return a string, number, or array of elements from the
render()
method. Naturally, this also applies to SSR.
So, now you can perform server-side rendering of components, which looks like this:
class MyArrayComponent extends React.Component { render() { return [ <div key="1">first element</div>, <div key="2">second element</div> ]; } } class MyStringComponent extends React.Component { render() { return "hey there"; } } class MyNumberComponent extends React.Component { render() { return 2; } }
You can even pass a string, number, or array of components to the top-level API method
renderToString
:
res.write(renderToString([ <div key="1">first element</div>, <div key="2">second element</div> ]));
This should allow you to get rid of any
div
and
span
that were simply added to your React component tree, which leads to a general reduction in the size of HTML documents.
React 16 generates more efficient HTML
If we talk about reducing the size of HTML-documents, then React 16, in addition, radically reduces the excessive load created by SSR when generating HTML-code. In React 15, each HTML element in an SSR document has a
data-reactid
, the value of which is a monotonically increasing ID, and the text nodes are sometimes surrounded by comments with
react-text
and ID. To see this, consider the following code fragment:
renderToString( <div> This is some <span>server-generated</span> <span>HTML.</span> </div> );
In React 15, this snippet will generate HTML code that looks like the one shown below (line breaks have been added to improve the readability of the code):
<div data-reactroot="" data-reactid="1" data-react-checksum="122239856"> This is some <span data-reactid="3">server-generated</span> <span data-reactid="5">HTML.</span> </div>
In React 16, however, all IDs are removed from the markup, as a result HTML, obtained from the same code fragment will be much simpler:
<div data-reactroot=""> This is some <span>server-generated</span> <span>HTML.</span> </div>
This approach, in addition to improving the readability of the code, can significantly reduce the size of HTML documents. This is just great!
React 16 supports arbitrary DOM attributes
In React 15, the DOM rendering system was quite limited in terms of attributes of HTML elements. She cleaned non-standard HTML attributes. In React 16, however, both client and server rendering systems now omit arbitrary attributes added to HTML elements. To learn more about this innovation, read the
post of Dan Abramov in the React blog.
SSR in React 16 does not support error handlers and portals
There are two new features in the React client rendering system that, unfortunately, are not supported in SSR. These are Error Boundaries and Portals. Error handlers are dedicated to the
excellent post of Dan Abramov in the React blog. Note, however, that (at least for now) handlers do not respond to server errors. For portals, as far as I know, there is not even an explanatory article yet, but the Portal API requires a DOM node, as a result, it cannot be used on the server.
React 16 performs less rigorous client side checking.
When you restore the markup on the client side in React 15, the call to
ReactDom.render()
performs a character-by-character comparison with the server markup. If for any reason a mismatch is detected, React issues a warning in design mode and replaces the entire markup tree generated on the server with HTML generated on the client.
In React 16, however, the client rendering system uses a different algorithm to validate the markup that came from the server. This system, in comparison with React 15, is more flexible. For example, it does not require the markup created on the server to contain the attributes in the same order in which they would be located on the client side. And when the client rendering system in React 16 detects discrepancies, it only tries to change a different HTML subtree instead of the entire HTML tree.
In general, this change should not have a particularly strong effect on end users, except for one fact: React 16, when calling
ReactDom.render() / hydrate()
, does not fix the mismatched HTML attributes generated by SSR. This performance optimization means that you will need to be more careful about fixing markup inconsistencies that lead to the warnings you see in
development
mode.
React 16 does not need to be compiled to improve performance.
In React 15, if you use SSR in the form in which it appears immediately after installation, performance is far from optimal, even in
production
mode. This is due to the fact that in React there are many great warnings and tips for the developer. Each of these warnings looks like this:
if (process.env.NODE_ENV !== "production") {
Unfortunately,
it turns out that
process.env
is not an ordinary JavaScript object, and accessing it is a costly operation. As a result, even if the value of
NODE_ENV
set in
production
, frequent checking of the environment variable significantly slows down server rendering.
To solve this problem in React 15, you would need to compile the SSR code to remove references to
process.env
, using something like the
Environment Plugin in a Webpack, or the
transform-inline-environment-variables plugin for Babel. I know from experience that many do not compile their server code, which, as a result, significantly degrades the performance of SSR.
React 16 resolves this issue. There is only one call to check the
process.env.NODE_ENV
at the very beginning of the React 16 code, as a result, it is no longer necessary to compile the SSR code to improve performance. Immediately after installation, without additional manipulations, we get excellent performance.
React 16 features higher performance
Continuing to talk about performance, we can say that those who used React server-side rendering in production often complained that large documents are processed slowly, even with all the recommendations for improving performance. I would like to note here that it is recommended to always check that the
NODE_ENV
variable is set to
production
when you use SSR in production.
I am pleased to announce that, having conducted some
preliminary tests , I found a significant increase in the performance of server rendering React 16 on various versions of Node:
Server rendering in React 16 is faster than in React 15. The lower the bar, the better the result.When comparing with React 16, even taking into account the fact that in React 15, the calls to
process.env
were eliminated due to compilation, there is a performance increase of about 2.4 times in Node 4, 3 times in Node 6, and a remarkable increase of 3.8 times in Node 8.4. If you compare React 16 and React 15 without compiling the latter, the results on the latest version of Node will be amazing.
Why is React 16 so much faster than React 15? So, in React 15, the server and client rendering subsystems were, in general terms, the same code. This means the need for virtual DOM support during server rendering, even considering that this vDOM was dropped as soon as the
renderToString
call was
renderToString
. As a result, a lot of unnecessary work was done on the server.
In React 16, however, the development team rewrote server-side rendering from scratch, and now it is completely independent of vDOM. This gives a significant increase in productivity.
Here I would like to make one warning regarding the expected growth in the performance of real projects after switching to React 16. My tests were to create a huge
<span>
tree with one very simple recursive component React. This means that my benchmark belongs to the category of synthetic and almost certainly does not reflect the actual use of React scenarios. If your components have many complex
render
methods that take many CPU cycles to process, React 16 cannot do anything to speed them up. Therefore, although I expect to see an acceleration of server rendering during the transition to React 16, I do not expect, say, a threefold increase in productivity in real applications. According to untested data, when using React 16 in a real project, it was possible to achieve a
productivity increase of about 1.3 times . The best way to understand how React 16 will affect the performance of your application is to try it yourself.
React 16 supports streaming
The last of the new features of React, which I want to talk about, is no less interesting than the rest. This is rendering directly to the Node streams.
Stream rendering can reduce the time to receive the first byte (TTFB, Time To First Byte). The beginning of the document enters the browser before creating a continuation of the document. As a result, all leading browsers will quickly begin to parse and render the document.
Another great thing that can get from rendering to a stream is the ability to respond to a situation where the server issues data faster than the network can receive it. In practice, this means that if the network is overloaded and cannot receive data, the rendering system will receive the appropriate signal and suspend data processing until the load on the network drops. The result will be that the server will use less memory and be able to respond faster to I / O events. Both that and another is capable to help the server to work normally in difficult conditions.
In order to organize streaming rendering, you need to call one of two new
react-dom/server
methods:
renderToNodeStream
or
renderToStaticNodeStream
, which correspond to the
renderToString
and
renderToStaticMarkup
. Instead of returning a string, these methods return a
Readable object. Such objects are used in the model for working with Node flows for entities generating data.
When you get the
Readable
stream from the
renderToNodeStream
or
renderToStaticNodeStream
, it is in suspend mode, that is, the rendering has not yet started at this point. Rendering will start only if
read is called, or, more likely, you connect the
Readable
stream using a
pipe to the
Writable stream. Most Node web frameworks have a response object that inherits from
Writable
, so you can usually just redirect
Readable
to the response.
Let's say the above Express example could be rewritten for streaming rendering as follows:
// Express import { renderToNodeStream } from "react-dom/server" import MyPage from "./MyPage" app.get("/", (req, res) => { res.write("<!DOCTYPE html><html><head><title>My Page</title></head><body>"); res.write("<div id='content'>"); const stream = renderToNodeStream(<MyPage/>); stream.pipe(res, { end: false }); stream.on('end', () => { res.write("</div></body></html>"); res.end(); }); });
Note that when we redirect the stream to the response object, we need to use the optional argument
{ end: false }
to tell the stream that it should not automatically complete the response when rendering is complete. This allows us to complete the layout of the body of the HTML document, and, as soon as the stream is fully recorded in the response, complete the answer yourself.
Pitfalls Streaming Rendering
Stream rendering can improve many SSR scenarios, however, there are some patterns that will not benefit from streaming data.
In general, any template in which, based on the markup created during server rendering, data is generated that needs to be added to the document prior to this markup, it will be fundamentally incompatible with streaming data. Examples of this include frameworks that dynamically determine which CSS rules to add to the page in the previous generated markup
<style>
, or frameworks that add elements to the document’s
<head>
tag when rendering the document body. If you are using these frameworks, you will probably have to use regular rendering.
Another template that is not yet working in React 16 is the built-in
renderToNodeStream
calls in the component trees. The usual thing in React 15 is to use
renderToStaticMarkup
to create a page template and embed
renderToString
calls to form dynamic content. For example, it might look like this:
res.write("<!DOCTYPE html>"); res.write(renderToStaticMarkup( <html> <head> <title>My Page</title> </head> <body> <div id="content"> { renderToString(<MyPage/>) } </div> </body> </html>);
However, if you replace these calls of the rendering subsystem with their stream analogs, the code will stop working.
Readable
streams (which are returned from
renderToNodeStream
) are not yet possible to be embedded into components as elements. I hope this feature will be added to React.
Results
So, above, we looked at the main innovations of server rendering in React 16. I hope you liked them just as much as I did. In conclusion, I want to say a huge thank you to everyone who participated in the development of React 16.
Continue to read? In fact, it’s time to start tying and try to render something.
Dear readers! Are you here? Looks like you already experienced server rendering in React 16. If so, please share your impressions.