Setup React Server Side Rendering …QUICK!
One of the main advantages you get with React other than its intelligent Diffing algorithm coupled with its Virtual DOM, Component based architecture is Server Side Rendering.
By implementing server side rendering, you alleviate yourself from this problem all whilst enjoying react. You see traditional websites/Wordpress sites or any back end that uses a templating engine responds to a get request to their main website with HTML content. But like mentioned, with React since all your content and essentially entire website is a giant JS file, you have to wait for this code to be executed and “Rendered” when it lands on the clients browser, with SSR, you send back executed and rendered HTML.
The process generally involves fetching seed data either from a database request or from an api endpoint of your own domain, the resolved data is injected into the preloaded state and is used on initial render to the client.
Another advantage you get with SSR, is an extremely snappy website, since that initial rendering step is already taken care of so all that is left is to have this content, latch onto the client DOM or as referred to as — Mount onto the DOM. This helps us in segregating execution code, any code that may have external dependencies can be placed on a React components “componentWillMount” lifecycle function; and this will only execute on the Mount.
Alright, lets get into the setup, I’ll be using a NODE.JS backend, but assume similar principles for perhaps a PHP/any other back end.
The folder structure is as such
Let’s walk through this.
- Components contains the main App React component that withholds the connected WeatherData container which is located in the containers folder
- RootReducer amalgamates all the reducers that make up the global state and returns a pure object, which will be used by the createStore method for generating the redux store.
- Since this application is routed on the front end, the routes are curated in the routes.js file
- ConfigureStore.js acts as a point to create the redux store, along with hydrating this store with any initial data whilst enhancing it with middleware such as thunks or promise resolvers
- Index.js acts as an entry point for the for our module bundler, this is where we render our React application to the DOM.
- Server.js will host this project and contain logic for all server side rendering.
Lets go through each of these:
We import React and createComponent function from the React module . We render the children of this component which will be hosted in the routes folder.
Another straightforward file, this invokes a method on mounting, again — this would not be touching until it reaches the front end, as mounting is independent of rendering.
A switch statement reacts to the action provided to it and returns a completely new version of the initial state with the respective changes instructed by the action.
Using the combineReducers function from redux, we pass in an switch statement function that takes in an initial state as well as an action object which mutates it. In this example, for simplicity, we’ve restricted ourselves to one state, however, in most cases there would be multiple — justifying the use of combineReducers (used for example purposes)
Using the IndexRoute we map — WeatherApp as a child of the App component
The file default exports a function which is invoked by an argument that acts as an initial state to the application. We will late go on to understand that this initialState was send from the back end.
We pull in two functions from the ‘redux’ module:
applyMiddleware takes in the imported/custom middleware.
createStore, which takes as its arguments, the rootReducer (which we’ll look into very soon), an initial hydrating state and an enhancer-which in this case is the applyMiddleware function. You note the existence of the compose function, without going into too much detail, the purpose of this is to rightReduce all the middlewares that would be provided.
Read into this post to better understand the redux architecture.
Another configuration you can also look into is below, the flexibility arises from the architecture that is Redux.
The differentiating factor being, when using createStore with middleware, the second argument is the preloaded state, in a situation your server is not optimised to load any initial data, you are forced to leave that argument undefined. Although there might be a situation where on mounting a component you want to go ahead and fetch data from an API and load that into the store, sortof similar to initial state load, in such a situation using the configuration above helps, as applyMiddleware itself returns a function that would be invoked with the createStore function which then would be invoked by its three respective arguments, the reducers — initial state — and additional enhancers.
This is the main entry point file, as stated, in the client side of the application. There will be some code repetition between this file and the server.js file which is completely intentional. This is what will help with the speed, when the client receives any new data, instead of re-rendering it will use the rendered HTML for differentiating and injecting only the necessary changes.
This will look similar to most stock react app configurations, the difference appearing in line 10, this will make more sense once we jump into the server file, however to get your feet wet, essentially — we load in the initial state from the server generated HTML stored as global window variable under the __PRELOADED_STATE__ attribute . When this is received on the client end, we inject into our store as the initial state (hydration), which the client side will use on initiation.
Alright there’s a few things happening here — lets go through them line by line.
We invoke the express module and work of this instantiation.
The purpose of the renderFullHTMLPage function, is to inject our initial HTML component along with the preloaded state into a template that would be rendered on the client side. To send the state, we add a script tag that will attach this preloaded state to the global window object under the __PRELOADED_STATE__ attribute. The bundle.js file will contain the bundled outcome of your bundler and will contain the entire application.
We dangerouslySetTheInnerHTML as a method of sanitizing data and preventing XSS attacks as we’re injecting data to HTML. Read into this to understand the utility of __html.
Moving on, line 29 helps initiate our initial store using the configureStore method we created earlier.
Using the renderToString method extracted from ‘react-dom/server’ we create a stringified version of the client side application instantiation. We still need to pass the initial state to the renderFullHTMLPage function; since configureStore returns the redux store, along with helper methods — we use them to get access to the pure state-done on line 37.
And with that simple configuration, you now have client side rendering of your react application.
If you look into the github project you’ll see in the server file, that i’ve requested for data from an API endpoint, I await for it to resolve and pass this data i receive to the initialStore. This is then picked up on the front end via a console.log, whose output you can see in you log.
Note: if you want to use this to test, make sure you register for an API from openweather.org and place it in on line 18.
Hope this helps!