The Journey from SPA to Isomorphic

This article is based upon my journey of moving a single page application (SPA) to isomorphic.

Isomorphic: It is a Single Page Application(SPA) with server-side rendering.

We all are familiar with the features of SPA - highly interactive website, quick response time and ability to use it as offline once the page loads. Disadvantages of SPA are- long load time(bad UX), bad SEO(bad for business).

To overcome these disadvantages I decided to move my single page application to isomorphic.

Here is the architecture of my single page application(SPA) in blocks and sequences.


So, my SPA page rendering on the browser involves three steps:

Step 0. This is build(pre-runtime) step. Webpack Service uploads all assets(JS, CSS, Images, fonts files) to CDN and generates a manifest file. Java server reads this manifest file and injects these assets link in the index.html file.

Step 1. From the browser, the request comes with URL and server after completing processing of other pieces of stuff, returns generated index.html.

Step 2. The browser parses, raise requests to fetch required resources from CDN or server and execute them.

In this spa_home.html page, the browser will fetch “…/app-bundle.js” and “…/vendor-bundle.js”.

After download, a browser will execute these scripts which will add the following markup in <div id=”root”></div>

Ok, till now I have told you V(view) part of my application.

My application uses React-Redux MVC pattern. Now I will explain MC as well.

C(controller): dispatcher + actions. For isomorphic we don’t need its complete knowledge.

M(model): Redux state is our model for the app. It is nothing but a normal object which gets updated with actions dispatched from the controllers.

//output: {"loggedIn":true,"data":[]}

Restrain yourself from diving deep for now. We don’t need to know everything for isomorphic.

Now we know SPA with React-Redux MVC pattern. Let’s understand SSR.

Server-Side Rendering (SSR)

Server-Side Rendering(SSR) is nothing but giving initial Model and View to a browser.

In the above ssr_home.html complete View(HTML) is coming from the server.

Only change here to notice is “window.REDUX_STATE”. This is the redux state (Model) for our application. Ignore this for now. It will be explained and used later.

Finally, Isomorphic Application

Isomorphic means rendering pages on both server and client side.

Isomorphic = SPA(Single Page Application) + SSR(Server-Side Rendering)

To convert my SPA to isomorphic I did the following architectural changes.

My Isomorphic Design

Step 0. (Same as previous) This is build(pre-runtime) step. Webpack Service uploads all assets(JS, CSS, Images, fonts files) to CDN and generates a manifest file. Java server reads this manifest file and injects these assets link in the index.html file.

Step 1. From the browser, the request comes with URL(route). The server calls new node service with an input of route information.

Step 2. My node service takes the decision based upon the route information. 1) It first calls the required data API for the route component to load. The component which needs to be rendered is our route component.

2) Store the fetched API data into the redux-store. This redux-store will be used while rendering the route component.

3) Render the route component and convert into a string. Use an HTML template to append redux-state as a string into this.

Step 3. Node Service returns the generated HTML with embedded redux-state. Java Server will inject this response in <div id=“root”>{ISO-Inject}</div> of the index.html.

Step 4. Java Server will return generated index.html . Browser parse and execute and raise a request to fetch required resources from CDN or server. But this time <div id=“root”></div> is not empty. Pre-rendered markup is there. When react will mount the route component on the browser, it will find Virtual DOM to DOM diff is null and skip the update. We have returned the redux-state in the HTML response. Will use this to initialize the redux store on the browser. So we don’t need to call fetch data API from our initial route component.

Steps to change my SPA to Isomorphic App

Client Side

Step 1: We have given the browser client-side code in app-bundle.js which was generated from our all assets. Now we have to add logic to read window.REDUX_STATE. and add it to the redux store.

const store = createStore( window.REDUX_STATE || {} );

The redux-store will be created with initial values from the global object “REDUX_STATE”. M(model) one part has been solved.

Step 2: Additionally, add check at data fetch API call, because we already might have the data in the model.

componentDidMount( ) {
if ( this.props.myData.length <= 0 ) {
this.props.fetchMyData( );

Step 3: V(view) part is already coming from the server. We have to tell react to consider it while rendering.

const root = document.getElementById("root");
//ReactDOM.render(<App />, root);
ReactDOM.hydrate( <App />, root );

ReactDOM.hydrate is the same as render(), but is used to hydrate a container whose HTML contents were rendered by ReactDOMServer. React will attempt to attach event listeners to the existing markup.

ReactDOMServer? I will use and explain in the next steps.

Server Side

Step 4: Create a node service, which will listen to routes calls.

const app = express();
app.get( "/*", ( req, res ) => {

Step 5: Create redux-store and dispatch the initialized-session action. This is to tell browser store that session was initialized on the server.

Step 6: Fetch API data required for route component to load. We have to define the static declaration on each component which needs data to fetch. This fetch will update the created redux store.

Step 7: Pass redux store and route info to generate the required JSX string.

const reactDom = ReatDOM.renderToString(
<ReduxProvider store={ store }>
<StaticRouter location={ req.url }>
<Layout />

Step 8: Send response by appending the redux-store data.

This htmlTemplate function will take reactDom from the above step and generate the required HTML.

The End

You must have found this useful, “Press clap”.