SSR with React

Swarup Karavadi
4 min readJul 14, 2017

--

Sunset — Goa

What is SSR?

SSR stands for Server Side Rendering. It is a technique used to improve the perceived page load times.

In a non-SSR scenario, the react app is served up as a bunch of static files. The following steps (more or less) take place before the user can see the first render of the app —

  • Browser sends a request to a URL
  • Browser receives the index.html as a response
  • Browser then sends requests to download any remote links or remote scripts.
  • The browser waits till the scripts are downloaded
  • The react library renders the component passed to it and finally mounts the component to the specified DOM element.
// All dependencies have to be loaded before the below
// code executes.
const renderApp = () => render(
<App />,
document.querySelector("#mount")
);
  • Until the above piece of code executes, the user basically just sees a blank screen. This is not normally a problem but can quickly get frustrating on mobile devices — even on 4G connections.

Enter SSR

The video below demonstrates the improvement in perceived page load times when SSR the technique is used —

Screen capture demonstrating the difference in perceived page load times for non SSR (left) and SSR (right). Network throttling enabled and set to ‘Regular 4G’ on chrome dev tools.

As you can see, even though for both non-SSR and SSR pages, the amount of time taken for DOMContentLoaded is almost the same, the non-SSR page is just blank until all the scripts are downloaded. The SSR version on the other hand is visible immediately, even though the scripts are loading in the background.

How does SSR work —

  • Browser sends a request to the URL
  • A node server responds by rendering the corresponding component into a string —
// code on the server that renders the App to string. renderToString // is a function imported from 'react-dom/server'.export const renderApp = (html, req, res) => {
let appString = renderToString(<App />);
let renderedApp = html.replace("<!--ssr-->", appString);
res.status(200).send(renderedApp);
};
  • The rendered component is injected into index.html
  • The index.html is sent back to the browser
  • The browser renders the index.html and downloads all the other dependencies
  • Once all the scripts are loaded, the react component is rendered again on the client. The only difference this time is that it hydrates the existing view instead of overwriting it.

Hydrating a view means that it attaches any event handlers to the rendered DOM elements but keeps the rendered DOM elements intact. This way, the state of your DOM elements is preserved and there is no view reset that happens.

Structuring your code for SSR

From experience, I find that the best way to structure your code for server side rendered apps is to have as much of the codebase as possible to be isomorphic, i.e., capable of running on both the browser and on the server. To this end, I end up structuring my code like this —

As can be seen, I have a common folder where almost my entire app goes in. I have a client and a server folder where bootstrap code specific to the browser and the server environment are run. So if you use libraries like apollo, aphrodite or redux which require platform (browser/server) specific ways of initialising them, you’ll have to put the initialisation code in the client/index.js or server/app.js respectively.

Code samples below —

// app.js
import React from "react";
export const App = () => <h2>Hello, World!</h2>;
// client/index.js
import React from "react";
import { render } from "react-dom";
import { App } from "common/App";
const renderApp = () => render(<App />, document.querySelector("#mount"));
renderApp();
if (module.hot) {
module.hot.accept("common/App", () => {
renderApp();
});
}
// server/app.js
import React from "react";
import { renderToString } from "react-dom/server";
import { App } from "common/App";
export const renderApp = (html, req, res) => {
let appString = renderToString(<App />);
let renderedApp = html.replace("<!--ssr-->", appString);
res.status(200).send(renderedApp);
};

You can find the complete source code here

Designing Your Components For SSR

  • If you are using a css-in-js library, make sure it comes with SSR support.
  • Push all the data requirements of your component hierarchy up to the root. This way, you can fetch all the data on the server, before rendering your app. Once the data is available, you pass it to the root component, and the root component keeps passing that data down all the way to the leaf nodes. Render the app and then return it in the response.
  • Designing a large app with the above point in mind is borderline impossible. So it helps using a state container like redux which can be initialised with all the data that is required and nodes in the component hierarchy can just ‘connect’ to the store.

--

--