Responsive renderToString () Performance and caching of responsive components

I noticed that the reactDOM.renderToString() method starts to slow down significantly when rendering a large component tree on the server.

Background

A bit of background. The system is a completely isomorphic stack. The top-level component of the App displays templates, pages, dom elements, and other components. Looking at the reaction code, I found that it renders ~ 1500 components (this applies to any simple dom tag, which is considered to be a simple component, <p>this is a react component</p> .

In development, rendering ~ 1500 components takes ~ 200-300ms. By removing some components, I was able to get ~ 1200 components for visualization in ~ 175-225 ms.

In production, renderToString for ~ 1500 components takes about 50-200 ms.

Time seems linear. None of the components are slow, but rather the sum of many.

Problem

This creates some problems on the server. A lengthy method results in longer server response times. TTFB is much higher than it should be. For API calls and business logic, the response should be 250 ms, and when rendering 250 ms, it will double! Bad for SEO and users. In addition, being a synchronous method, renderToString() can block the host server and create backup copies of subsequent requests (this can be solved using 2 separate host servers: 1 as a web server and 1 as a service designed solely to respond to rendering) .

Attempts

Ideally, rendering a ToString in production would require 5-50 ms. I worked on some ideas, but I'm not quite sure what the best approach would be.

Idea 1: Component Caching

Any component that is marked as “static” can be cached. By renderToString() cache with visualized markup, renderToString() can check the cache before rendering. If it finds a component, it automatically captures the string. Doing this on a high-level component will save the mounting of all nested child components. You need to replace the rootID of the reactive partitioning of the rootID component with the current rootID.

Idea 2: Labeling Components as Simple / Mute

By defining a component as “simple,” the reaction should skip all life cycle methods when rendering. React already does this for the main response components ( <p/> , <h1/> , etc.). It would be nice to extend custom components to use the same optimization.

Idea 3: Skip components when rendering on the server side

Components that should not be returned by the server (without an SEO value) may simply be skipped on the server. Once the client boots up, set the clientLoaded flag to true and pass it down to force re-rendering.

Closing and other attempts

The only solution I have implemented so far is to reduce the number of components displayed on the server.

Here are some of the projects we're looking at:

Has anyone encountered similar problems? What could you do? Thank you

+58
performance reactjs react-dom isomorphic-javascript
Jan 11 '16 at 18:55
source share
4 answers

Using response-router1.0 and react0.14, we mistakenly serialize our stream object several times.

RoutingContext will call createElement for each pattern in your response routes. This allows you to enter any details you want. We also use stream. We are sending a serialized version of a large object. In our case, we did flux.serialize() inside createElement. The serialization method may take ~ 20 ms. With 4 patterns, this will be an additional 80 ms for your renderToString() method!

Old code:

 function createElement(Component, props) { props = _.extend(props, { flux: flux, path: path, serializedFlux: flux.serialize(); }); return <Component {...props} />; } var start = Date.now(); markup = renderToString(<RoutingContext {...renderProps} createElement={createElement} />); console.log(Date.now() - start); 

Easily optimized for this:

 var serializedFlux = flux.serialize(); // serialize one time only! function createElement(Component, props) { props = _.extend(props, { flux: flux, path: path, serializedFlux: serializedFlux }); return <Component {...props} />; } var start = Date.now(); markup = renderToString(<RoutingContext {...renderProps} createElement={createElement} />); console.log(Date.now() - start); 

In my case, it helped to reduce renderToString() from ~ 120 ms to ~ 30 ms. (You still need to add 1x serialize() ~ 20ms to the total, which happens before renderToString() ). It was a good quick improvement. - It is important to remember that always do everything right, even if you do not know the direct impact!

+13
Jan 20 '16 at 17:57
source share

Idea 1: Component Caching

Update 1 : I added a full working example below. It caches components in memory and updates data-reactid .

This can actually be done easily. You should monkey-patch ReactCompositeComponent and check for the cached version:

 import ReactCompositeComponent from 'react/lib/ReactCompositeComponent'; const originalMountComponent = ReactCompositeComponent.Mixin.mountComponent; ReactCompositeComponent.Mixin.mountComponent = function() { if (hasCachedVersion(this)) return cache; return originalMountComponent.apply(this, arguments) } 

You must do this before require('react') somewhere in your application.

Note from the web package: If you use something like new webpack.ProvidePlugin({'React': 'react'}) , you should change it to new webpack.ProvidePlugin({'React': 'react-override'}) , where you make your changes to react-override.js and export react (i.e. module.exports = require('react') )

A complete example that caches in memory and updates the reactid attribute might be the following:

 import ReactCompositeComponent from 'react/lib/ReactCompositeComponent'; import jsan from 'jsan'; import Logo from './logo.svg'; const cachable = [Logo]; const cache = {}; function splitMarkup(markup) { var markupParts = []; var reactIdPos = -1; var endPos, startPos = 0; while ((reactIdPos = markup.indexOf('reactid="', reactIdPos + 1)) != -1) { endPos = reactIdPos + 9; markupParts.push(markup.substring(startPos, endPos)) startPos = markup.indexOf('"', endPos); } markupParts.push(markup.substring(startPos)) return markupParts; } function refreshMarkup(markup, hostContainerInfo) { var refreshedMarkup = ''; var reactid; var reactIdSlotCount = markup.length - 1; for (var i = 0; i <= reactIdSlotCount; i++) { reactid = i != reactIdSlotCount ? hostContainerInfo._idCounter++ : ''; refreshedMarkup += markup[i] + reactid } return refreshedMarkup; } const originalMountComponent = ReactCompositeComponent.Mixin.mountComponent; ReactCompositeComponent.Mixin.mountComponent = function (renderedElement, hostParent, hostContainerInfo, transaction, context) { return originalMountComponent.apply(this, arguments); var el = this._currentElement; var elType = el.type; var markup; if (cachable.indexOf(elType) > -1) { var publicProps = el.props; var id = elType.name + ':' + jsan.stringify(publicProps); markup = cache[id]; if (markup) { return refreshMarkup(markup, hostContainerInfo) } else { markup = originalMountComponent.apply(this, arguments); cache[id] = splitMarkup(markup); } } else { markup = originalMountComponent.apply(this, arguments) } return markup; } module.exports = require('react'); 
+6
Jul 25 '16 at 8:29
source share

This is not a complete solution. I had the same problem with my isomorphic application to respond, and I used several things.

1) Use Nginx in front of your nodejs server and cache the received response for a short time.

2) When displaying a list of items, I use only a subset of the list. for example, I will only display X elements to fill the viewport, and load the rest of the list on the client side using Websocket or XHR.

3) Some of my components are empty when rendering on the server side and will only load from client-side code (componentDidMount). These components are typically graphs or components associated with a profile. these components usually do no good from an SEO point of view

4) About SEO, from my experience of 6 months with an isomorphic application. Google Bot can easily read the React web page on the client side, so I'm not sure why we are worried about server-side rendering.

5) Save <Head > and <Footer> as a static line or use a template engine ( Reactjs-handellbars ) and render only the contents of the page (several rendered components should be saved in it). In the case of a one-page application, you can update the title description in each navigation inside Router.Run .

+5
Jan 17 '16 at 6:25
source share

I think fast-react-render might help you. This increases the rendering performance of your server three times.

To do this, you just need to install the package and replace ReactDOM.renderToString with FastReactRender.elementToString:

 var ReactRender = require('fast-react-render'); var element = React.createElement(Component, {property: 'value'}); console.log(ReactRender.elementToString(element, {context: {}})); 

You can also use fast-react-server , in which case the render will be 14 times faster than traditional rendering. But for this, each component that you want to render must be declared with it (see the example in quick-react-seed, how you can do this for webpack).

+4
Sep 12 '16 at 13:39
source share



All Articles