Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React app with Server-side rendering crashes with load

I'm using react-boilerplate (with react-router, sagas, express.js) for my React app and on top of it I've added SSR logic so that once it receives an HTTP request it renders react components to string based on URL and sends HTML string back to the client.

While react rendering is happening on the server side, it also makes fetch request through sagas to some APIs (up to 5 endpoints based on the URL) to get data for components before it actually renders the component to string.

Everything is working great if I make only several request to the Node server at the same time, but once I simulate load of 100+ concurrent requests and it starts processing it then at some point it crashes with no indication of any exception.

What I've noticed while I was trying to debug the app is that once 100+ incoming requests begin to be processed by the Node server it sends requests to APIs at the same time but receives no actual response until it stops stacking those requests.

The code that's used for rendering on the server side:

async function renderHtmlDocument({ store, renderProps, sagasDone, assets, webpackDllNames }) {
  // 1st render phase - triggers the sagas
  renderAppToString(store, renderProps);

  // send signal to sagas that we're done
  store.dispatch(END);

  // wait for all tasks to finish
  await sagasDone();

  // capture the state after the first render
  const state = store.getState().toJS();

  // prepare style sheet to collect generated css
  const styleSheet = new ServerStyleSheet();

  // 2nd render phase - the sagas triggered in the first phase are resolved by now
  const appMarkup = renderAppToString(store, renderProps, styleSheet);

  // capture the generated css
  const css = styleSheet.getStyleElement();

  const doc = renderToStaticMarkup(
    <HtmlDocument
      appMarkup={appMarkup}
      lang={state.language.locale}
      state={state}
      head={Helmet.rewind()}
      assets={assets}
      css={css}
      webpackDllNames={webpackDllNames}
    />
  );
  return `<!DOCTYPE html>\n${doc}`;
}

// The code that's executed by express.js for each request
function renderAppToStringAtLocation(url, { webpackDllNames = [], assets, lang }, callback) {
  const memHistory = createMemoryHistory(url);
  const store = createStore({}, memHistory);

  syncHistoryWithStore(memHistory, store);

  const routes = createRoutes(store);

  const sagasDone = monitorSagas(store);

  store.dispatch(changeLocale(lang));
  
  match({ routes, location: url }, (error, redirectLocation, renderProps) => {
    if (error) {
      callback({ error });
    } else if (renderProps) {
      renderHtmlDocument({ store, renderProps, sagasDone, assets, webpackDllNames })
        .then((html) => {
          callback({ html });
        })
        .catch((e) => callback({ error: e }));
    } else {
      callback({ error: new Error('Unknown error') });
    }
  });
}

So my assumption is that something is going wrong once it receives too many HTTP requests which in turn generates even more requests to API endpoints to render react components.

I've noticed that it blocks event loop for 300ms after renderAppToString() for every client request, so once there are 100 concurrent requests it blocks it for about 10 seconds. I'm not sure if that's a normal or bad thing though.

Is it worth trying to limit simultaneous requests to Node server?

I couldn't find much information on the topic of SSR + Node crashes. So I'd appreciate any suggestions as to where to look at to identify the problem or for possible solutions if anyone has experienced similar issue in the past.

like image 853
George Borunov Avatar asked Feb 05 '18 21:02

George Borunov


People also ask

Can React be rendered on server side?

Yes! This is where server-side rendering for React comes in. In this article, I want to introduce you to server-side rending (SSR) with React, reasons to use it, and some popular frameworks for rendering React on the server side.

Is React SSR or CSR?

In fact, it was Facebook's release of the React library that popularised a CSR approach to applications by making it more technologically accessible. On the other hand, a web-based app serving mainly static content, like this website, would be expected to opt for an SSR approach.

Does useEffect work server side?

useEffect never runs on the server. Even if your page is pre-rendered, whatever code you include in a useEffect hook will not be part of the server response.

Does React support client-side rendering?

Moreover, there is no need to reload the entire UI after every call to the server. The client-side framework manages to update UI with changed data by re-rendering only that particular DOM element. Today, Angular and React. js are some of the best examples of libraries used in client-side rendering.


2 Answers

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

like image 93
Abhay Shiro Avatar answered Sep 18 '22 14:09

Abhay Shiro


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

like image 45
user1095118 Avatar answered Sep 17 '22 14:09

user1095118