score:2

The calls to renderToString() are synchronous, so they are blocking the thread while they are running. So its no surprise that when you have 100+ concurrent requests that you have an extremely blocked up queue hanging for ~10 seconds.

Edit: It was pointed out that React v16 natively supports streaming, but you need to use the renderToNodeStream() method for streaming the HTML to the client. It should return the exact same string as renderToString() but streams it instead, so you don't have to wait for the full HTML to be rendered before you start sending data to the client.

score:3

1. Run express in a cluster

A single instance of Node.js runs in a single thread. To take advantage of multi-core systems, the user will sometimes want to launch a cluster of Node.js processes to handle the load.

As Node is single threaded the problem may also be in a file lower down the stack were you are initialising express.

There are a number of best practices when running a node app that are not generally mentioned in react threads.

A simple solution to improve performance on a server running multiple cores is to use the built in node cluster module

https://nodejs.org/api/cluster.html

This will start multiple instance of your app on each core of your server giving you a significant performance improvement (if you have a multicore server) for concurrent requests

See for more information on express performance https://expressjs.com/en/advanced/best-practice-performance.html

You may also want to throttle you incoming connections as when the thread starts context switching response times drop rapidly this can be done by adding something like NGINX / HA Proxy in front of your application

2. Wait for the store to be hydrated before calling render to string

You don't want to have to render you layout until your store has finished updating as other comments note this is a blocks the thread while rendering.

Below is the example taken from the saga repo which shows how to run the sagas with out the need to render the template until they have all resolved

  store.runSaga(rootSaga).done.then(() => {
    console.log('sagas complete')
    res.status(200).send(
      layout(
        renderToString(rootComp),
        JSON.stringify(store.getState())
      )
    )
  }).catch((e) => {
    console.log(e.message)
    res.status(500).send(e.message)
  })

https://github.com/redux-saga/redux-saga/blob/master/examples/real-world/server.js

3. Make sure node environment is set correctly

Also ensure you are correctly using NODE_ENV=production when bundling / running your code as both express and react optimise for this

score:3

enter image description here

In the above image, I am doing ReactDOM.hydrate(...) I can also load my initial and required state and send it down in hydrate.

enter image description here

I have written the middleware file and I am using this file to decide based on what URL i should send which file in response.

enter image description here

Above is my middleware file, I have created the HTML string of the whichever file was requested based on URL. Then I add this HTML string and return it using res.render of express.

enter image description here

Above image is where I compare the requested URL path with the dictionary of path-file associations. Once it is found (i.e. URL matches) I use ReactDOMserver render to string to convert it into HTML. This html can be used to send with handle bar file using res.render as discussed above.

This way I have managed to do SSR on my most web apps built using MERN.io stack.

Hope my answer helped you and Please write comment for discussions


Related Query

More Query from same tag