A comparison of Server Side Rendering in React and Angular applications

Handling API calls with SSR

Since no application is complete without API calls, we will also implement a sample api call to fetch data from https://jsonplaceholder.typicode.com/posts and show it on the UI. This would also help us understand how to make API calls on the server side and render them to the template before returning the response back to the client.

To enable HTTP calls, we first need to import the HttpClientModule in the AppModule so that we can make the API calls. Then we include the HttpClient in the component of our choice to dispatch the actual API requests.

Http Calls in Non SSR mode

This is especially helpful during local development. Since we are making requests to a third party server, we can easily do it via proxy which can be enabled in Angular simply by adding a proxy configuration. Since the api server does not have a /api prefix to the calls, we will add it temporarily and remove it right before proxying so that we can differentiate between the API calls and the UI state changes (for consistency between SSR and non-SSR modes).

For the proxy configuration, we create a JSON file at the root of our project as shown below:

Notice the pathRewrite option above which indicates that we are stripping out the /api prefix that we will add to the requests.

And to enable proxy, we need to change our start script in package.json to the following:

"start": "ng serve --proxy-config proxy.conf.json",

We can now add our logic in any component to fetch the data, below is an example for HomeComponent:

and to show these posts on the template:

Http Calls in SSR mode

In SSR mode, the API calls are triggered in two ways:

  1. When the template is initially rendered on the server i.e. component gets loaded
  2. When the template is rendered on the client (i.e. while transitioning out of the server rendered template)

The only difference between two ways listed above is how the API calls is triggered and handled.

Since we are making the API call directly from the template on the server, we need to provide an absolute URL while making the request.

In the next stage, since our template makes the API call from the client, we can capture the request on our web server and proxy it to the API server thus bypassing CORS limitations.

For #1, API calls straight from the server, we need to update the application to able to communication with the API server directly, we can do that by passing in a new provider APP_BASE_HREF in the Server Module with the base URL value of our API server and then consume it in our API calls as shown below:

we can then inject and use it in our HomeComponent as shown below:

We are checking if we have a PLATFORM_ID only for the log statement, this can be removed if necessary.

The URL construction above is where the magic happens. On the server, when we are making a request straight out of the template, we skip the the /api prefix and when we do it from the client, we append it and let our Express server capture, modify and proxy the request for us.

Since the API call will be made on the client and server a few seconds apart, it is strongly recommended that you implement some form of caching to make the API loads faster or use a state store such as

ngrx.

For #2, when the server side rendered template is being transitioned out by the client version, we need to update the way our server works. We now need to proxy the requests from our web server to an external API server. To achieve this, we can pipe the requests which Express allows. We can use any HTTP requests library (such as request in this case) to facilitate the piping:

npm i -S request

And then we replace the existing API logic as shown below in our server.ts file:

This captures all the requests which start with the prefix /api and then removes it from the URL before forwarding the request to the our API server.

Build And Run

The last thing to do before we start our server would be to compile it (since we have written it in typescript). With all the changes incorporated, the final state of the scripts section of the package.json file is as follows:

To build and serve the project now, run the following commands:

npm run build:ssr
npm run serve:ssr

Since we have added the log statements, we should be able to see the logs as shown below on the server:

And on the browser:

We can see that one second after the template is rendered and returned to the client, the client transitions out from the SSR provided template to regular application and re-renders the route.

Any and all subsequent navigations between the routes is all going to be performed in the client and would only be rendered on the server if a full page reload is performed.

Entire code base for the project can be found here and the changes specific to SSR can be found here.

SSR in React applications

In React application, for simple projects, the setup is pretty minimal. However, the tricky part is to handle the multiple libraries which we may end up using in our project. We could run into potential issues if wish to render a route on the server side which uses an incompatible library. Luckily, most common and frequently used libraries all provide SSR capabilities so we should be fine in majority of the cases. For the sake of simplicity, we will not be including a lot of libraries in our sample application below.

Similar to the Angular section, let us list out the changes that we will have to introduce so that our application is SSR compatible:

  1. We now need a server — instead of using something like NGINX to serve our build folder, we will now use a server (NodeJS/Express in this example) for the same to enable SSR.
  2. Mandatory use of Redux (or the likes) if we want to show API data which is SSR’ed
  3. Conditional checks to avoid reloading of data which was loaded on server during SSR and updated in the store
  4. Static declaration of API calls on components to facilitate SSR

Basic Application Setup

Similar to the Angular application, we first create the react application using create-react-app which consists of a few basic routes.

We will be using create-react-app to create the application. Let’s begin:

create-react-app react-ssr
cd react-ssr

Let’s add the necessary libraries to enable routing

npm i -S react-router react-router-dom

Next, let us set up the basic components which we wish to load with each of the routes, at this stage they all look alike as shown below:

Let us also create a routing file that we can read and display on our base component which is in App.js. This file could also be where we provide additional information in case we use something like react-helmet.

Update App.js to show the routes listed above:

The application can now be started and we can navigate to all the routes defined above.

Server Side Rendering Setup

The setup necessary for making the application SSR ready is fairly simple in React as compared to Angular (at least, when there are no API calls to make). There is no extra code or any special jazz needed to make the application render on the server.

What we do need is a server (Express in this case), which can serve static files and understand what route the user is trying to access, set that route as the application location and then render the application. We can also pass additional context in case we need it in the form of static context. All of this is provided by the react-router library in the form of StaticRouter as opposed to the BrowserRouter which is generally used on the client side.

Let us create folder server and add the following server.js file to it which contains 3 different middleware for loading the static resources and handling our routing logic:

since there is some JSX involved, we will install and use @babel/core, @babel/cli, @babel/preset-env and @babel/preset-react so that node can understand our server.js file:

npm i -S express @babel/core @babel/cli @babel/preset-env @babel/preset-react

Then to compile this server.js file, we can invoke a small babel script inline as shown below:

babel server/server.js --out-file server/index.js [email protected]/env,@babel/react

Once the server is compiled successfully, we are now ready to run our application. But before we do that, we will need to ignore the styles of our components when rendering our template on the server side, the styles will however be applied from the main.css file that is generated and placed in the build folder as a part of our final distribution that is created. To ignore these styles, we could use the ignore-styles npm plugin.

Ignoring styles would mean that when the server initially returns our template, it would be not be styled, which leads to flicker of the page once it is rendered on the client, for now, we are going to ignore this issue but it can be fixed using webpack and

isomorphic style loader which can be seen from this very simple example.

Since our compiled server file is still trying to access App module we need to compile the code for which we can use the @babel/register plugin and provide the necessary presets to compile at runtime.

Put this all together and start the server. We can use the below script labelled start.js at the root of the project.

We can now add this invocation to the package.json under scripts to make things easier for us.

Handling API calls with SSR

Before we run and test our changes, let us also add a simple API call which retrieves the data from http://jsonplaceholder.typicode.com/posts as we did in case of the Angular example earlier.

To make API calls, we will use Axios npm package. Typically, to render API response on the UI. We would make the API call once the component is mounted using componentDidMount life cycle hook and then call setState to store the response of the API call onto the component state. This setState call triggers the render method again which now has access to the updated state.

Since we are compiling our component on the server and are not really mounting it to an actual DOM, we cannot use the previously discussed approach as it does not trigger the componentDidMount life cycle on the server side. However, componentWillMount gets triggered on both the server and client side. This unfortunately cannot be used as it is an anti-pattern to cause any side-effects in the componentWillMount life-cycle hook.

The simplest solution to this problem is to use a state store like Redux.

Let us first talk about how it would work on the client side:

  1. Component loads and calls componentDidMount
  2. We check if data already exists in store, if not make the API call and update store
  3. Render the component

The same flow on the server side is going to be a little different:

  1. Determine which route a user is loading
  2. Determine the component which is associated with this route
  3. If there is a static data fetch method attached to retrieve data, do it.
  4. Update the store with the data retrieved
  5. Render the template with the store data
  6. Return the template and the store that has been created on the server.

Before we make any code changes, let us add all the necessary libraries.

npm i -S axios redux redux-thunk request

Http Calls in Non SSR mode

Since we want our application to run in both SSR mode (in production) and in non SSR mode (for local development). We can simply add a proxy to our package.json file to proxy our requests (similar to the Angular application) as shown below:

We can now make our API call in the componentDidMount life-cycle hook for any component of our choice.

And this works fine in the client only mode. Since we need to account for SSR mode as well, we have to modify the component to use Redux store to hold the posts which can be used on both the server and the browser.

To facilitate that, we need to create our Redux store first which in this case is pretty lean as shown below:

In the above case the actions, reducers, and the API calls are all in the same file for readability but you might want to keep this separated in your project.

We can now call the fetchPosts method from the Home component.

The only peculiar thing in this file is the static declaration of the API method onto the class, we will discuss the need for this in the next section.

Another weird quirk is that we can no longer define state at the root element like we usually do:

state = {
something: ''
};

This gives a runtime error which asks us to install @babel/plugin-proposal-class-properties. Instead, we need to wrap it within our constructor as shown below:

constructor(props) {
super(props);
this.state = {
something: ''
};
}

The last piece of change to tie all this together is the creation of the store when the application renders on the browser:

Http Calls in SSR mode

To enable API calls on the server, we can easily build on the changes laid down for the client side. The only modification needed would be to pass a valid default state (i.e. the data which was loaded on the server) to the store when the application is being rendered on the client.

To calculate the accurate state of the store on the server side, we need to first determine the actions which need to be dispatched (which can update our store with the necessary data). Our server with the change would appear as follows:

In our example, we are hydrating our main template with a single API call. In case we had multiple, we can simply add all the requests to the serverSideFetch method and that would ensure that all the data is loaded onto the store before the template is rendered.

One more thing to notice is that at the end, when we are rendering the template, we are also adding the current state of the store to window.REDUX_DATA. That way, when the application initializes on the client, we can simply read the value on the window object and pass it into our store creation. That way, we get to avoid duplicate calls on the client for the same data which was rendered on the server.

Also, we need to modify our base index.html file in the public folder to add the placeholder for the REDUX_DATA which we will replace on the server:

Build And Run

With those changes in, we are now ready to run our application using the scripts defined in package.json.

npm run build:ssr
npm run serve:ssr

On the client, we can see that the initial request that we triggered comes back with the data loaded on the server. And on the server, we see the log statements printed as expected.

Full code base for the example shown above is available here.

Comparison & Conclusion

Although both React and Angular are very different in terms of their core principals and building blocks, one thing that is common between the two is that enabling SSR is an easy task.

When compared to React, the amount of boiler plate code to be added in an Angular app is more since adding a server specific module which encapsulates the browser main module comes in a few different files.

The server side logic for SSR is very similar in both Angular and React thanks to the fact that Angular has wrapped Express and provided us with ExpressEngine that exposes easy to use methods (at least for NodeJS based backend).

In both Angular and React apps we require a compilation step on the server side, although it can be bypassed in Angular if we are using vanilla JavaScript instead of TypeScript but for React application we end up using babel since we introduce JSX in our server side code.

Since a lot of the functionality in React comes from additional libraries (router, redux etc.), we need to explicitly integrate them on the server side whereas an Angular application only needs access to the compiled main modules factory.

Making an API call is pretty much the same complexity in Angular and React applications. But, its only true if our React application is very basic, a majority of the project do end up needing some form of state store with growing complexity so using Redux in this case is definitely not a negative.

The code base for both the examples shown above are available here: Angular, React

read original article here