Innovations in server rendering in React 16
- Transfer
React 16 released! Talking about this event, I can mention a lot of great news (like the architecture of the Fibers kernel), but personally I am most admired about the improvements in server rendering. I propose to analyze and compare all this in detail with what it was before. I hope you enjoy server-side rendering in React 16 the way I liked it.
First, let's recall how Server-Side Rendering (SSR) looks in React 15. For SSRs, they usually support a Node-based server using Express, Hapi, or Koa and invoke it
When the client receives a response, the client rendering subsystem, in the template code, is given the command to restore the HTML generated on the server using the method
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?
The React development team showed a clear focus on backward compatibility. Therefore, if your code runs in React 15 without reporting obsolete constructs, it should just work in React 16 without any extra 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.
It should be noted that when switching from React 15 to React 16, you may come across the following warning in the browser.
Another useful React warning. 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. A method
In React 15, a component method
So, now you can perform server-side component rendering, which looks something like this:
You can even pass a string, number, or array of components to the top-level API method
This should allow you to get rid of any
If we talk about reducing the size of HTML documents, then React 16, in addition, radically reduces the excessive load created by the SSR when generating HTML code. In React 15, every HTML element in an SSR document has an attribute
In React 15, this snippet will generate HTML code that looks like the one shown below (line feeds added to improve code readability):
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:
This approach, in addition to improving the readability of the code, can significantly reduce the size of HTML documents. This is just great!
In React 15, the DOM rendering system was quite limited in terms of attributes of HTML elements. She cleaned up non-standard HTML attributes. In React 16, however, both the client and server rendering systems now skip arbitrary attributes added to HTML elements. To learn more about this innovation, read Dan Abramov 's React blog post.
There are two new features in the React client rendering system, which, unfortunately, are not supported in the SSR. These are Error Boundaries and Portals. An excellent post by Dan Abramov on the React blog is dedicated to error handlers . However, keep in mind 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.
When you restore client-side markup in React 15, the call
In React 16, however, the client rendering system uses a different algorithm to verify that the markup that came from the server is correct. This system, in comparison with React 15, is more flexible. For example, it does not require that the markup created on the server contain attributes in the same order in which they would be located on the client side. And when the client rendering system in React 16 discovers discrepancies, it only tries to change the distinct HTML subtree, instead of the entire HTML tree.
In general, this change should not particularly affect end users, except for one fact: React 16, when called
In React 15, if you use the SSR in the form in which it appears immediately after installation, the performance is far from optimal, even in mode
Unfortunately, it turns out that
In order to solve this problem in React 15, you would need to compile the SSR code to remove links to
React 16 resolved this issue. There is only one call to check
If we continue the discussion about productivity, we can say that those who used React server rendering in production often complained that large documents are processed slowly, even with all recommendations for improving performance. Here I want to note that it is recommended to always check that the variable is
I am pleased to report that, after conducting some preliminary tests , I found a significant increase in the performance of server rendering of React 16 on various versions of Node:
Rendering on a server in React 16 is faster than in React 15. The lower the bar, the better the result
When compared with React 16, even taking into account that React 15 calls
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 to support the virtual DOM during server rendering, even though this vDOM was dropped as soon as the call returned
In React 16, however, the development team rewrote server 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 performance growth of real projects after the transition to React 16. My tests consisted in creating a huge tree from
The last of the new React features that I want to talk about is no less interesting than the rest. This is rendering directly to Node streams.
Stream rendering can reduce the time it takes to get the first byte (TTFB, Time To First Byte). The beginning of the document enters the browser even before creating the continuation of the document. As a result, all leading browsers will begin to parse and render the document faster.
Another great thing that can be obtained from rendering into 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 an appropriate signal and stop processing data until the network load drops. As a result, it turns out that the server will use less memory and be able to respond more quickly to I / O events. Both that and another are 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 methods
When you receive a stream
Say the above Express example could be rewritten for streaming rendering as follows:
Please note that when we redirect the stream to the response object, we need to use an optional argument
Stream rendering can improve many SSR scenarios, however, there are some patterns that stream data will not benefit.
In general, any template in which, based on the markup created during the server rendering, the data that must be added to the document before this markup is formed, will be fundamentally incompatible with streaming data. Among examples of this are frameworks that dynamically determine which CSS rules should be added to a page in the previous generated markup tag
Ещё один шаблон, который ещё не работает в React 16 — это встроенные вызовы
Однако, если заменить эти вызовы подсистемы рендеринга на их потоковые аналоги, код перестанет работать. Потки
Итак, выше мы рассмотрели основные новшества серверного рендеринга в React 16. Надеюсь, вам они понравились так же, как и мне. В заключение хочу сказать огромное спасибо всем, кто участвовал в разработке React 16.
Продолжаете читать? Вообще-то, пора бы уже с этим завязывать и попробовать что-нибудь отрендерить.
Уважаемые читатели! Вы ещё здесь? Похоже, серверный рендеринг в React 16 вы уже испытали. Если так — просим поделиться впечатлениями.
How SSR works in React 15
First, let's recall how Server-Side Rendering (SSR) looks in React 15. For SSRs, they usually support a Node-based server using Express, Hapi, or Koa and invoke it
renderToString
to convert the root component to a string, which is then written server response:// используем Express
import { renderToString } from "react-dom/server"
import MyPage from "./MyPage"
app.get("/", (req, res) => {
res.write("My Page ");
res.write("");
res.write(renderToString());
res.write(" ");
res.end();
});
When the client receives a response, the client rendering subsystem, in the template code, is given the command to restore the HTML generated on the server using the method
render()
. The same method is used in applications that render on the client without server involvement:import { render } from "react-dom"
import MyPage from "./MyPage"
render(, 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 focus on backward compatibility. Therefore, if your code runs in React 15 without reporting obsolete constructs, it should just work in React 16 without any extra 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 come across the following warning in the browser.
Another useful React warning. 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. A method
render()
for situations where rendering is performed entirely on the client, and a method hydrate()
for cases where rendering on the client is based on the results of server rendering. Due to the backward compatibility of the new version of React, it render()
will work even if you pass to it what came from the server. However, these calls should be replaced by calls.hydrate()
so that the system stops issuing warnings, and in order 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(, document.getElementById("content"))
React 16 can work with arrays, strings and numbers.
In React 15, a component method
render()
should always return a single React element. However, in React 16, client-side rendering allows components to also return a render()
string, number, or array of elements from a method . Naturally, this also applies to SSR. So, now you can perform server-side component rendering, which looks something like this:
class MyArrayComponent extends React.Component {
render() {
return [
first element,
second element
];
}
}
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([
first element,
second element
]));
// Не вполне ясно, зачем так делать, но это работает!
res.write(renderToString("hey there"));
res.write(renderToString(2));
This should allow you to get rid of any
div
and span
that simply added to your React component tree, which leads to a general reduction in the size of HTML documents.React 16 Generates More Effective HTML
If we talk about reducing the size of HTML documents, then React 16, in addition, radically reduces the excessive load created by the SSR when generating HTML code. In React 15, every HTML element in an SSR document has an attribute
data-reactid
whose value is a monotonically increasing ID, and text nodes are sometimes surrounded by comments with react-text
and IDs. In order to see this, consider the following code fragment:renderToString(
This is some server-generatedHTML.
);
In React 15, this snippet will generate HTML code that looks like the one shown below (line feeds added to improve code readability):
This is some
server-generated
HTML.
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:
This is some server-generatedHTML.
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 custom DOM attributes
In React 15, the DOM rendering system was quite limited in terms of attributes of HTML elements. She cleaned up non-standard HTML attributes. In React 16, however, both the client and server rendering systems now skip arbitrary attributes added to HTML elements. To learn more about this innovation, read Dan Abramov 's React blog post.
SSR in React 16 does not support error handlers and portals
There are two new features in the React client rendering system, which, unfortunately, are not supported in the SSR. These are Error Boundaries and Portals. An excellent post by Dan Abramov on the React blog is dedicated to error handlers . However, keep in mind 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 stringent client-side validation
When you restore client-side markup in React 15, the call
ReactDom.render()
performs a character-by-character comparison with server-side 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 the HTML that was generated on the client.In React 16, however, the client rendering system uses a different algorithm to verify that the markup that came from the server is correct. This system, in comparison with React 15, is more flexible. For example, it does not require that the markup created on the server contain attributes in the same order in which they would be located on the client side. And when the client rendering system in React 16 discovers discrepancies, it only tries to change the distinct HTML subtree, instead of the entire HTML tree.
In general, this change should not particularly affect end users, except for one fact: React 16, when called
ReactDom.render() / hydrate()
, does not fix the mismatched HTML attributes generated by the SSR. This performance optimization means that you will need to be more careful about fixing markup mismatches that result in warnings that you see in mode development
.React 16 does not need to be compiled to improve performance
In React 15, if you use the SSR in the form in which it appears immediately after installation, the performance is far from optimal, even in mode
production
. This is because React has a lot of great warnings and tips for the developer. Each of these warnings looks something like this:if (process.env.NODE_ENV !== "production") {
// что-то тут проверить и выдать полезное
// предупреждение для разработчика.
}
Unfortunately, it turns out that
process.env
this is not an ordinary JavaScript object, and accessing it is a costly operation. As a result, even if the value is NODE_ENV
set to production
, frequent checking of the environment variable significantly slows down the server rendering. In order to solve this problem in React 15, you would need to compile the SSR code to remove links to
process.env
, using something like the Environment Plugin in 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 resolved this issue. There is only one call to check
process.env.NODE_ENV
at the very beginning of React 16 code, you no longer need to compile SSR code to improve performance. Immediately after installation, without additional manipulations, we get excellent performance.React 16 delivers better performance.
If we continue the discussion about productivity, we can say that those who used React server rendering in production often complained that large documents are processed slowly, even with all recommendations for improving performance. Here I want to note that it is recommended to always check that the variable is
NODE_ENV
set to a value production
when you use SSR in production. I am pleased to report that, after conducting some preliminary tests , I found a significant increase in the performance of server rendering of React 16 on various versions of Node:
Rendering on a server in React 16 is faster than in React 15. The lower the bar, the better the result
When compared with React 16, even taking into account that React 15 calls
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 to support the virtual DOM during server rendering, even though this vDOM was dropped as soon as the call returned
renderToString
. As a result, a lot of unnecessary work was done on the server.In React 16, however, the development team rewrote server 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 performance growth of real projects after the transition to React 16. My tests consisted in creating a huge tree from
one very simple recursive component of React. This means that my benchmark belongs to the category of synthetic ones and almost certainly does not reflect the real use cases of React. If your components have many complex methodsrender
, which takes many processor cycles, React 16 cannot do anything to speed them up. Therefore, although I expect to see accelerated server rendering when switching to React 16, I do not expect, say, a threefold increase in performance in real applications. According to unverified 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 React features that I want to talk about is no less interesting than the rest. This is rendering directly to Node streams.
Stream rendering can reduce the time it takes to get the first byte (TTFB, Time To First Byte). The beginning of the document enters the browser even before creating the continuation of the document. As a result, all leading browsers will begin to parse and render the document faster.
Another great thing that can be obtained from rendering into 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 an appropriate signal and stop processing data until the network load drops. As a result, it turns out that the server will use less memory and be able to respond more quickly to I / O events. Both that and another are 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 methods
react-dom/server
: renderToNodeStream
or renderToStaticNodeStream
, which correspond to the renderToString
and methods renderToStaticMarkup
. Instead of returning a string, these methods return an objectReadable . Such objects are used in the Node flow model for entities that generate data. When you receive a stream
Readable
from methods renderToNodeStream
or renderToStaticNodeStream
, it is in the pause mode, that is, rendering at this point has not yet begun. Rendering will only begin if you call read , or, more likely, connect the stream Readable
using pipe to the Writable stream . Most Node web frameworks have a response object that is inherited from Writable
, so you can usually just redirect Readable
to the response. 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("My Page ");
res.write("");
const stream = renderToNodeStream();
stream.pipe(res, { end: false });
stream.on('end', () => {
res.write(" ");
res.end();
});
});
Please note that when we redirect the stream to the response object, we need to use an optional argument
{ end: false }
to tell the stream that it should not automatically complete the response when rendering is complete. This allows us to finish the body of the HTML document, and as soon as the stream is completely recorded in the response, complete the answer ourselves.Pitfalls of streaming rendering
Stream rendering can improve many SSR scenarios, however, there are some patterns that stream data will not benefit.
In general, any template in which, based on the markup created during the server rendering, the data that must be added to the document before this markup is formed, will be fundamentally incompatible with streaming data. Among examples of this are frameworks that dynamically determine which CSS rules should be added to a page in the previous generated markup tag
, или фреймворки, которые добавляют элементы в тег
документа в процессе рендеринга тела документа. Если вы используете подобные фреймворки, вам, вероятно, придётся применять обычный рендеринг.Ещё один шаблон, который ещё не работает в React 16 — это встроенные вызовы
renderToNodeStream
в деревьях компонента. Обычное дело в React 15 — использовать renderToStaticMarkup
для создания шаблона страницы и встраивать вызовы renderToString
для формирования динамического содержимого. Например, это может выглядеть так:res.write("");
res.write(renderToStaticMarkup(
My Page
{ renderToString( ) }
);
Однако, если заменить эти вызовы подсистемы рендеринга на их потоковые аналоги, код перестанет работать. Потки
Readable
(которые возвращаются из renderToNodeStream
) пока невозможно встраивать в компоненты как элементы. Надеюсь, такая возможность ещё будет добавлена в React.Итоги
Итак, выше мы рассмотрели основные новшества серверного рендеринга в React 16. Надеюсь, вам они понравились так же, как и мне. В заключение хочу сказать огромное спасибо всем, кто участвовал в разработке React 16.
Продолжаете читать? Вообще-то, пора бы уже с этим завязывать и попробовать что-нибудь отрендерить.
Уважаемые читатели! Вы ещё здесь? Похоже, серверный рендеринг в React 16 вы уже испытали. Если так — просим поделиться впечатлениями.