Isomorphic Implementation of React

Yudhajit Adhikary
19 min readAug 23, 2019

--

What’s up guys,React is getting the popular day by day but with the advancement and day to day introduction of new concepts to React,Many of us are overlooking another important implementation of React that is ,Isomorphic implementation of React.So to start our discussion ,at fast we have to divide our discussion into several parts:

  1. What is Isomorphic process ?
  2. Advantages of Isomorphic process.
  3. Disadvantages of Isomorphic process.
  4. How sensitive data is Exposed ?
  5. Isomorphic Application Priority.
  6. Important Libraries used in Isomorphic process.
  7. Code Sharing , Data Injection , Server Rendering.
  8. ReactDOMServer.
  9. React Router ,React Router Server Rendering.
  10. Hot Module Replacement.
  11. Hot Module Replacement in Production.

1. What is Isomorphic Process ?

Isomorphic process is a process of rendering web-application on browser in following steps:

When an user requests web page , server queries for the database and Api and asks app for the HTML , and app would render given information it has ,then server sends client the rendered HTML,So when the application renders on the client device while the users are enjoying different content ,the client requests Javascript from server and bootstrap the application,Client application recognizes the pre-rendering HTML and binds it together.Thus Application finishes loading several seconds after we got the initial content.

2. Advantages of Isomorphic Process

There are two aspects of advantages of isomorphic process:

Functional Aspect:

  1. Code is shared between the front-end and back-end of the application.
  2. Faster load time and it provides automatic support for legacy browser.

Technical Aspect:

  1. The server render the HTML on initial page load.
  2. Less code to write and maintain .
  3. Developer have to learn fewer language.
  4. Data are stored in congruent data format(JSON Format).
  5. Library can integrate with itself.

3. Disadvantages of Isomorphic Process

The disadvantages of Isomorphic Process are as follows:

  1. The architecture of the whole application becomes complex.
  2. Troubleshooting is very much challenging due to the complex architecture of the application.
  3. Sensitive data can be prone to exposure on server.

4. Several points of possible failure.

The third point of the disadvantages leads us to next point of our discussion that is,

4. How Sensitive Data is Exposed?

In Isomorphic process,when Developer authorizes client facing module ,there is a dependency of the module which requires a file containing secret data,Application is bundled with that file and sent to client with all secret and private data which an attacker can easily access by searching the bundle file .

5. Isomorphic Application Priorities

There are several priorities of Isomorphic process for development as well as production environment of the application:

Development

  1. Application update quickly after source code.
  2. Additional round trip is required to fetch data .
  3. Code needs to be organised, clear and highly traceable.
  4. Tooling is needed to allow developer to see application’s internal state.

Production

  1. Application is not concerned with update to source.
  2. Additional round trips significantly slow application on mobile and greatly increase the odds of user closing the application before it even loads.
  3. Code should be compact and should not expose sensitive data
  4. Tooling is needed to track user activity and expose bugs.

6. Important libraries used in Isomorphic process

There are several libraries which are used in Isomorphic Web development those are:

React:

React renders the application on the server ,powers the application on the client and renders it,handles serving content based on routes (via React Router and Express)

Express:

Express is responsible for fetching data to create initial state.It uses React to send the application on server.It renders webpack instance in development to server app bundle .It also loads templates and servers static files.

Babel:

Babel is used by webpack compile all the ES2015 and JSX into client friendly ES5. It is used as Babel-CLI to serve top level script(i.e, Express server) written in ES2015.

Before Babel: const double=x=>x*2.

After Babel: function double(x){

return x*2;

}

Webpack:

Webpack in development environment compiles application into ES5 JavaScript and serves it. Webpack also injects updated modules directly into client (“hot loading”).It complies modules into a single static ES5 Javascript file for production.

7. Code Sharing,Data Injection and Server Rendering

Code Sharing:

One of the principle advantage of isomorphic web development is that the architecture uses some of the same code for client as for server.Server can load client libraries,making server rendering possible.

Data Injection:

Data Injection is the process in which Data is prepared by server ,that data is then converted into JSON and then injected into index.js ,this results a longer initial loading of the page and Client application has data immediately(No round trips).Let’s now understand the concept of Data injection in details,Data is prepared by server ,express get data from server,and convert it into JSON,the raw data that all the application needs to render as state,but not the actual HTML that is rendered , and when client Application load means the Javascript is ready.

Server Rendering:

Server rendering is the process in which Data is prepared by server, HTML for application is rendered on server by passing data to application,this still results in a longer initial load though it can be faster than data injection.Thus client application can see rendered view even though data is not yet fetched.

Difference between Data Injection & Server Rendering

8. ReactDOMServer

ReactDOMServer is a object that enables us to render components to static markup,ReactDOMServer generates static HTML Markup based on parent component and initial state ,leverage virtual DOM which is used on client to promote performance . ReactDOMServer is designed on server (No Browser Dependencies).

The ReactDOMServer are of two types:

  1. renderToString( )
  2. renderToStaticMarkup( )

Difference between renderToString( ) & renderToStaticMarkup( )

9. React Router

React Router renders components based on path,it integrates Redux using Connected Router,it also uses history module to inject custom path and works in client and on the server as well.

React Router Server Rendering:

Let’s discuss in details how React Router Server Rendering happens ,First Server receives request and notes path,it creates history components and injects path,then the application is wrapped in history component and rendered on server. React Router needs path from history to render the appropriate view .Pre-rendered HTML is sent directly to user ,subsequent route changes are handles in client.

10. Hot Module Replacement

Hot Module Replacement is the process by application gets updated without refreshing the browser.It follows ‘opt-in’ system where each module decides whether it can replace it’s own child module,It is easy to do when application is not having any local state. Let us discuss Hot Module Replacement in more depth.First client has application open ,whenever change in data occurs source module “child” is updated,server notifies client that updated module is ready, client then check if any modules will ‘accept’ the new code,Now Source modules “parent” accepts reload,loads new module and replace the old one,and client finally servers application updated without refreshing.

11. Hot Module Replacement in Production

In production ,Push updates to the application while your end user is using it,puts more stress on server as it struggle to keep thousands of client instances update. Totally untested-security vulnerabilities are a serious concern , but the advantages are ample.

12. Implementation

So enough of theory now I think it will be the best time to drive into creating an Isomorphic react application.

So we must first have to know what application we are developing,so we are developing an application which is fetching data from a github api and rendering into our application the github api is having live data each question in the question list is having title,taglist and button (More Info) on clicking on the button it will show the questionDetails that’s it a very simple application ,but it’s the perfect example to make your concept clear about isomorphic application of react.So Let’s start coding:

Folder Structure of the Application:

Whenever we are creating a project or an application specially when we are starting writing code for a react application we should always take care of proper folder structure of our code base, So first we will focus on the code base structure or folder structure:

Folder Structure of our Application

So let start from the beginning isomorphic-react-master is the folder inside which we are having data folder which will consist of mock data and Api links for fetching data, dist folder will be formed when we will run webpack - -config webpack.config.dev.babel.js,we will see about that file later for now just keep in mind that it webpack.config.dev.babel.js is a file what bundle a javascript into single file that our compiler understand, and bundle.js is that file that our compiler understand.node_modules is the file which is formed when we do npm init for the first time in the terminal it contains the settings of all the libraries and npm packages used in the application development.Public folder is the folder which is having the index.html the html page that is rendering in the browser or the starting point of our application.Next is server which stores the index.jsx file which controls the whole rendering ,routing and isomorphic functional part of the application. src is consist of components,reducers,sagas, App.jsx, getStore.jsx, index.jsx and .babelrc .Components are the react components rendering in the Dom , Reducers are the state manipulater in redux,sagas are the file where we have used Redux-saga which is a redux middleware library, that is designed to make handling side effects in our redux app nice and simple. App.jsx is rendering all the react components, getStore.jsx is the store house of all the states in the application and managing the states. index.jsx is the maintaining routing and functional part of the application and lastly .babelrc is used to config babel for our application.We will go through each and every file and understand the code inside each of them. package.json is a json file that consist of all the dependencies required for the development of the application. webpack.config.dev.babel.js and webpack.config.prod.babel.js are the webpack configuration file for the development and production environment which will actually create the bundle.js file inside dist folder.Now let’s start with webpack.config.dev.babel.js:

webpack.config.dev.babel.js
webpack.config.dev.babel.js

webpack.config.dev.babel.js is used to bundle a javascript into single file that our compiler understand. path is mostly there to resolve inconsistency between linux and windows file system,Development webpack config designed to be loaded by express development server, The scripts in entry are combined in order to create our bundle,entry is the list of file, babel will load automatically and assemble our packages,think of it like put the script tag in our index.html.inside entry, Webpack hot middleware enables hot reloading.reload?true causes the page to reload when no hot reload handler is specified.babel-regenerator-runtime lets us use generators and yield. path.resolve(__dirname, ‘src/’) src is the entry point of the main application, — dirname is simply a variable that always points to the folder that we are working in . Output contains detailed information about the bundle.js. In this case, bundle.js is never created by but it is created by webpack-dev-middleware in ./server .Public path is necessary for webpack HMR to reload correctly when on a path other than ‘/’, and bundle.js is the name of the final output. webpack.HotModuleReplacementPlugin( ) is needed for Hot module reloading, webpack.NamedModulesPlugin() causes the relative path of the module to be used in HMR. webpack.DefinePlugin({‘process.env’: {}}) defines the env as ‘development’, which triggers different behaviors in some scripts . Resolve allows files to be imported without specifying an extension as long as they match one specified, i.e. import component from ‘./component’.If we try to run webpack now,it will not show anything,because we are not telling use babel to tranfer our code ,to tell this we use module: {loaders: []} and we say that the files ending with .jsx is to be loaded by using babel loader means babel loader is used for any JS or JSX files in the src directory. Now let’s see webpack.config.prod.babel.js:

webpack.config.prod.babel.js
webpack.config.prod.babel.js

It is same as webpack.config.dev.babel.js only difference is that it is webpack configuration file for production environment it’s having little bit of changes like we have used Uglifies JS which improves performance, React will throw console warnings if this is not implemented. Now let’s talk about bundle.js. bundle.js gets automatically created when we run the webpack config file in the terminal, by the command- webpack- -config webpage.config.dev.babel.js. Bundle.js is the single file which bundles all the javascript file in our application that our compiler understand. Now let’s configure babel using .babelrc:

.babelrc

Without babel there is no ES-6, no Redux, no Saga ,basically no isomorphic react application at all.So first we will create a .babelrc file[babel reads to figure out what is it’s configuration and where it going to be used. presets are that array that babel is going to use “@babel/preset-react” let us compile Jsx code into basic javascript file.We use “@babel/preset-env” as superior to a babel-print ES2016,ES2015 etc because while babel preset ES2016 only provide support for a certain subset of ES6 ,babel-preset-env automatically support whatever feature are required to make the application compatible with the browser that is targetted. “@babel/plugin-proposal-object-rest-spread” is used as plugin which let us use object spread syntax which is very much required for redux based application.Now let’s see the two files inside data folder : mock-questions.json and api-real-url.js

api-real-url.js

In api-real-url we are exporting two variables, questions which is having Api link of stack overflow of all the question asked.It is the URL to receive a list of questions in JSON from StackOverflow. and question is the URL to receive details on a single question.This request also returns the body of the question with a parameter (id,which is the question ID to fetch). mock-question.json is the json file which store the same data which is present in the api but in Json format means mock-question.json actually mocking the data of the api.Now let’s talk about two file present inside saga: fetch-question-saga.js and fetch-questions-saga.js:

fetch-questions-saga.js

In fetch-questions-saga.js we are fetching the list of the questions from the ‘/api/question’ route of our application , you must have thinking where from this ‘REQUEST_FETCH_QUESTIONS’ and ‘api/question’ route is coming ,we will express this after a while but for now keep in mind that whenever we receive the message ‘REQUEST_FETCH_QUESTIONS’ ,we take a variable name raw where we store all the list of the questions coming from the route .I hope you people know the function of yield ,yield is used inside generator to make sure that the whole line is executed completely before moving to the next line,so when the whole list of question is fetched from the api ,we are converting the raw data into json format using .json( ) .and finally storing the item array of the json (array of the question) in the variable questions,and last yield put({type:`FETCHED_QUESTIONS`,questions}) indicates that new questions have been loaded and pass the fetched questions with the message.Now let’s see the fetch-question-saga.js:

fetch-question-saga.js

In fetch-question-saga.js we are fetching the details of the each question from the ‘/api/questions/${question_id}’ route of our application,where question_id is the id of the question ,it is almost same as fetch-question-saga.js here we are creating question variable which is storing the item[0] from json which stores the question details, and last

yield put({type:`FETCHED_QUESTION`,question}) indicates that new questions have been loaded and pass the fetched question details with the message. So who will receive the put messages which are send by these saga file,this request will be received by the reducer who is actually responsible for changing state which are stored inside store. So Let’s see the reducer folder ,which is having two files index.js and questions.js,

questions.js

In questions.js we are importing unionWith from ‘lodash/unionWith’ , Questions reducer, deals mostly with actions dispatched from sagas. Question Equality returns true if two questions are equal, based on a weak check of their question_id property a is the first question b is the second question and it returns{boolean} value of whether the questions are equal or not.Now when fetch-question -saga.js and fetch-questions-saga.js put the FETCHED_QUESTION and FETCHED-QUESTIONS message the reducer receives the message and update the state by adding the new fetched questions into the state depending on the value of questionEquality and return the state.Now comes index.js:

index.js inside reducer

Index.js exports of the module which is an object where the keys are the names of the properties the reducers operate on,and the value is the actual reducer.Now let create our stores using getStore.js

getStore.js

getStore.js is the file which is the store of all the states used in the application,So let’s start understanding the code line by line,we have imported createStore, combineReducer and applyMiddleware from ‘redux’ to create stores, apply reducers and middleware ,then we have imported routerReducer and routerMiddleware to apply router in between the store. For creating Logger and SagaMiddleware we have import createLogger and createSagaMiddleware from redux-logger and redux-saga. We have also imported two Saga files and reducers . So getStore creates a new instance of the store configurable for use on the client (for a living app) and the server (for pre-rendered HTML). history is a history component which could be browserHistory for client, and memoryHistory for server. defaultState is the default state of the application. Since this is used by React Router, this can affect the application’s initial render.

const middleware = routerMiddleware(history);

Create middleware for React-router and pass in history.

const sagaMiddleware = createSagaMiddleware();

Create saga middleware to run our sagas.After that we create an array name middlewareChain and pass middleware and sagaMiddleware into the array, after that we create a logger to provide insights to the application’s state from the developer window and push that logger inside the middlewareChain. Then we create the store by using createStore( ) and passing all the reducers, defaultState and middleware to that store. Lastly we use SagaMiddleware.run( ) to run the sagas which will in turn wait for the appropriate action type before making requests.Finally we are returning the store to the caller for application initialization.Now let’s move on to index.jsx inside src folder:

Index.js
Index.js

index.js is the main entry point of the client application and is loaded by Webpack. It is not loaded by the server at any time as the configurations used (i.e.,browserHistory) only work in the client context. The server may load the App component when server rendering.We have imported provider from react-redux to make the states inside store available to all the components,ConnectedRouter from react-router-redux to perform routing,getStore and createhistory from history/createBrowserHistory, createhistory is used to create Browser history for routing. So first we have created history and store calling createhistory and passing that history to the getstore method respectively.If using hot module reloading, watch for any changes to App or its descendent modules.Then, reload the application without restarting or changing the reducers, store or state. Then we are render the app, encapsulated inside a router, which will automatically let route tags render based on the Redux store, which itself is encapsulated inside a provider, which gives child connected components access to the Redux store. store.subscribe is listening for changes to the store, when there is any changes in the store, store.getState function is called and stored in state,if(state.questions.length >0) means when the questions array is populated, that means the saga round trip has completed,and the application can be rendered. Rendering before the questions arrived would result in the server-generated content being replaced with a blank page.fetchDataForLocation reads the current path, which corresponds to the route the user is seeing, and makes a request to the appropriate saga to fetch any data that might be required. we are using location to get the current URL that is loaded.If location.pathname= ‘/’, means if the location is the standard route, fetch an undetailed list of all questions, and if location.pathname.includes(‘questions’) means if the location is the details route, fetch details for one question.After that Initializing data fetching procedure and listening to changes in path, and trigger data fetching procedure on any relevant changes. Now lets see the index.html inside public folder:

index.html

In Index.html we have added bootstrap link for styling and inside AppContainer we have placed <%preloadedApplication%> means the application is rendered here when React bootstrap, overwriting any prerendered HTML within.The bundle.js is automatically generated by Webpack and contains the entire application. Now let’s see index.js file inside server:

server/index.js
server/index.js
server/index.js
server/index.js
server/index.js

In Index.js we get basic configuration settings from arguments.When useServerRender is true, the application will be pre-rendered on the server. otherwise, just the normal HTML page will load and the app will bootstrap after it has made the required AJAX calls.When useLiveData is true, the application attempts to contact Stackoverflow and interact with its actual API. Now if(process.env.NODE_ENV === ‘development’){},this block will run during development and facilitates live-reloading means if the process is development, we have to set up the full live reload server.

const config = require(‘../webpack.config.dev.babel.js’).default;

we are getting the development configuration from webpack.config.

const compiler = webpack(config);

we are creating a webpack compiler which will output our bundle.js based on the application’s code.

app.use(require(‘webpack-dev-middleware’)(compiler,{

noInfo: true,

stats: {……}

}));

noInfo only display warnings and errors to the console, stats option lets you precisely control what bundle information gets displayed. If the process is production, just serve the file from the dist folder, and build should have been run beforehand.

app.use(express.static(path.resolve(__dirname, ‘../dist’)));

getQuestions returns a response object with an [items] property containing a list of the 30 or so newest questions . If live data is used, we contact the external API, else if live data is not used, we read the mock questions file. Lately parsing the data and returning it.

Similarly getQuestion returns a specific question details ,If live data is used, contact the external API,else if live data is not used, get the list of mock questions and return the one that matched the provided ID and creating a mock body for the question and returning the details(data).then we are creating an api route localhost:3000/api/questions, which returns a list of questions using the getQuestions utility inserting a small delay there so that the async/hot-reloading aspects of the application are more obvious. Similarly getQuestion returns detailed information on a single question for the special route , then we create a route that triggers only when one of the two view URLS are accessed on that route we read the raw index HTML file, then create a memoryHistory, which can be used to pre-configure our Redux state and routes, then by setting initialEntries to the current path, the application will correctly render into the right view when server is rendering, also create a default initial state which will be populated based on the route. then we are checking to see if the route accessed is the “question detail” route or not by :

if (req.params.id) { }

if there is req.params.id, this must be the question detail route. if it is true we get the question that corresponds to the request, and preload the initial state with it,else we are on the “new questions view”, so preload the state with all the new questions (not including their bodies or answers).

const store = getStore(history,initialState);

Create a redux store that will be used only for server-rendering our application (the client will use a different store).If server render is used, replace the specified block in index with the application’s rendered HTML by surrounding the application in a provider with a store populated with our initialState and memoryHistory .else if server render is not used, just output a loading message, and the application will appear when React boostraps on the client side.

app.listen(port, '0.0.0.0', ()=>

console.info(`Listening at http://localhost:${port}`));

then we will listen on the specified port for requests to serve the application.Now let’s see App.js file:

App.js

App Component is the highest level real component in the application, it is the parent of the routes and an ancestors of all other components I don’t think much elaborate explanation of this file is needed it is simply using the redux concept. mapStatetoProps is taking the states from the store and converting it into the props of the component so that the components can access the states and connect helps to connect react with redux. Let’s see the components that are rendering in the App.js let’s start with Questionlist.js inside components folder:

QuestionList.jsx

In QuestionList each entry is represtented by a QuestionListItem, which displays high-level information about a question in a format that works well in a list,QuestionList displays all questions in an array provided to it as a simple list.MapStateToProps get the list of questions from the application’s state.It is populated by a ../sagas/fetch-question(s)-saga and it converts all the states into props.And finally create and export a connected component (QuestionList) now inside QuestionListItem you might have seen the Taglist in which we are passing tags as props.It is component which is having all the Tag user have used while asking the question in stackoverflow ,Let’s see the Taglist component now:

TagList.js

In Taglist component the tags props is coming in the form of array so we are mapping through that array and placing them in between the code tag.Now let’s see QuestionDetails.js which show the question details when we click on the more info button:

QuestionDetails.jsx

QuestionDetails display outputs a view containing question information when passed a question as its prop. If no question is found, that means the saga that is loading ,it has not completed, and display an interim message. MapStateToProps finds the question in the state that matches the ID provided and pass it to the display component as a props,and the component is finally connected and exported. So after long discussion of both theoretical and practical part of the isomorphic react Application,our Isomorphic React Application is finally ready:

Output: localhost:3000
Output: localhost:3000/questions/:id

You can find code on my GitHub repo:

CODE:

So it’s really a long and complex discussion so kudos to all who successfully complete the article ,it’s really very much informative,So I always encourage you to play with this concept , more you explore these react concepts,more it will become interesting.HAPPY CODING….:)

--

--

Yudhajit Adhikary

Web developer by profession,Photographer,Blog Writer,Singer,Pianist,Seeker of Solution