kolkergordon_aitiwaa_00 By Elyse Kolker Gordon

This article has been excerpted from Isomorphic Development with JavaScript

 

Isomorphic Web App Overview

To understand what an isomorphic web app is, we’re going to use an example web app called All Things Westies. On this site, you can find places to adopt a Westie (West Highland White Terrier, a small white breed of dog). You can also get information about Westies, purchase dog supplies and buy products featuring Westies (socks, mugs, shirts, etc.). Because this is an ecommerce app, we care about having a good Search Engine Optimization (SEO) presence and we want our customers to have a great experience using the app.

How it works

Look at Figure 1, which is a wireframe for the All Things Westies app. There’s a standard header with main site navigation on the right. Below the header, the main content areas promote products, blog posts and the social media presence.


kolkergordon_aitiwaa_01

Figure 1 A wireframe showing the home page for allthingswesties.com, an isomorphic web app.


This site, allthingswesties.com, is an isomorphic web app. The first time you visit the site, the content is rendered and served to the browser by the server. This is done using server-rendered techniques with NodeJs. As you navigate around the pages, looking for a dog or supplies, each page is rendered by the JavaScript running in the browser using SPA techniques.

In order to make this work, there’s a third piece in the mix. I think of this part as the “isomorphic handoff.” On the server, you save the state of the application and then provide this state to the browser. The browser uses this state to bootstrap the SPA version of the application. Without this isomorphic handoff, the user must wait for the server-rendered page to load, and then wait longer for a complete re-render of the content in the browser.

The All Things Westies app relies on reusing as much code as possible between the server and the browser. It uses JavaScript’s ability to run in multiple environments: JavaScript runs in browsers and also runs on the server via Node.js. JavaScript can run on a lot of other places as well, such as on Internet of Things devices and on mobile devices via React Native, but we’re going to keep the focus on web apps that run in the browser.

An isomorphic app is a web app that blends a server-rendered web app with a single-page application. On the one hand, we want to take advantage of fast perceived performance and SEO-friendly rendering from the server. On the other hand, we want to handle complex user actions in the browser (e.g. opening a modal overlay). We also want to take advantage of the browser push history and XMLHttpRequest (XHR) abilities to hit the server less frequently.

INFO XMLHttopRequests (XHR) are also known as Ajax (Aynchronous JavaScript and XML) calls. Ajax calls use the XHR API in browsers.

Many of the concepts in this book could be applied without writing all of the code in JavaScript. Historically, the complexity of running an isomorphic app without being able to reuse code has been prohibitive. It’s possible to server-render your site with Java or Ruby and then transition to a single page app, but it isn’t commonly done because it requires duplicating large portions of code in two languages. This has a high cost in the maintenance of an app.


kolkergordon_aitiwaa_02

Figure 2 Isomorphic apps build and deploy the same JavaScript code to both environments.


To see this flow in action, take a look at Figure 2. It shows how the code for All Things Westies gets deployed to the server and the browser. Because we take advantage of JavaScript running in both environments, the same code that runs in the browser, and talks to our API or data source, also runs on the server to talk to our backend. Next we’ll take a look at the specifics of implementing an isomorphic app with JavaScript.


Building our stack

Building an app like All Things Westies requires putting together several well-known technologies. It’s possible to build an isomorphic app using few or no libraries, but it’s highly recommended to take advantage of the JavaScript communities’ efforts in this area.

TIP Make sure any libraries you include in an isomorphic app support running in both the server and browser environments.

The HTML components that display the products and supplies, i.e. the view, will be built with React. We’ll use a Flux-like data architecture via Redux, the current community standard for Flux-like data management in React apps. We’ll explore using webpack to manage what code runs in the browser and to enable running Node module code in the browser.

On the server side, we’ll build a Node.js server using Express to handle routing. We’ll take advantage of React’s ability to render on the server, and use it to build up a complete HTML response that can be served to the browser. Figure 3 shows how all these pieces fit together.


kolkergordon_aitiwaa_03

Figure 3 Building the stack for the server and the browser. Entry points, shared code and build types


To make our application work everywhere, we’ll build them in a way that pre-fetches data for our routes using React Router. We’ll also handle differences in environments by building separate code entry points for the server and browser. In cases where code can only be run in the browser, we’ll gate the code or take advantage of the React lifecycle to ensure code won’t run on the server.


Architecture Overview

Earlier in this article, I told you how an isomorphic application is the combination of a server-rendered application and a single-page application, and uses both in the same application architecture. To get a better understanding of how we connect the concepts of a server-rendered application and a single-page application, let’s refer to Figure 4.

Application Flow


kolkergordon_aitiwaa_04

Figure 4 Isomorphic application flow


Figure 4 shows all the steps involved in getting an isomorphic app rendered and responding to user input, like a single-page application, starting when the user enters the web address.

Every web app session is initiated when a user navigates to the web app or types the URL into the browser window. For allthingswesties.com, when a user clicks on a link to the app from an email or from searching on Google, the flow on the server goes through the following steps.

  • The server receives a request.
  • Our application router handles the request and gathers the data required for the part of our application being requested. If the request is for allthingswesties.com/mugs, the app requests the list of mugs for sale through the site. This list of mugs, along with information to be displayed
    (names, descriptions, price, images), is collected before moving on to the render step.
  • The server generates the HTML for our web page using the data collected for the mugs page.
  • The server responds to the request for allthingswesties.com/mugs with the fully-built HTML.

The next part of the application cycles the initial load in the browser. We differentiate the first time the user loads the app from subsequent requests because several things will only happen once during this first load.

Definition Initial load is the first time the user interacts with our website. This means the first time the user clicks a link to our site in a Google search, from social media or types it directly into the web address bar.

The first load on the browser begins as soon as the HTML response from the server’s received and the DOM is processed.

  • The browser starts to render the mugs page immediately because the HTML sent by the server is fully formed, with all of the content we generated on the server. This includes the header and the footer of our app, along with the list of mugs for purchase. The app won’t respond to user input at this point, like adding a mug to the cart, or viewing the detail page for a specific mug.
  • When the browser reaches the entry JavaScript for our application, the application bootstraps. This will be at the end of the <body> tag.
  • The virtual DOM is recreated in React. Because the server sent down the app state, this virtual DOM is identical to the current DOM.
  • Nothing happens! React finds no differences between the DOM and the virtual DOM it built. The user is already being shown the list of mugs. The application can respond to user input now, like adding a mug to the cart.

At this point, single-page application flow takes over and the app responds to user input, browser events, timers, etc. The user can add products to their cart, navigate around the site, and interact with complex slideshows for pictures of products and dogs.

  • The application responds to user input, like clicking add to cart.
  • The virtual DOM is rebuilt once any calls have been made to our cart APIs.
  • React diffs the virtual and browser DOM
  • Updates are made and any repaints are executed. The users cart icon updates to show that an item has been added.

Handling the server-side request

Now let’s dive in a little deeper and take a closer look at what happens when the server receives the initial request to render the page. First let’s look at what part of the site renders on the server. Figure 5 is like the one at the beginning of the article (Figure 1) but it doesn’t render the twitter widget. The Twitter widget is designed to be loaded in the browser and doesn’t render on the server.


kolkergordon_aitiwaa_05

Figure 5 The server-rendered version of the All Things Westies home page.


The server does two important things. First, it fetches the data required for the view. Then it takes that data and uses it to render the DOM. Let’s check out Figure 6, which shows the flow on the server.


kolkergordon_aitiwaa_06

Figure 6 Server Render the Page


  1. The server receives a request.
  2. The server fetches the required data for that request. This can be from either a persistent data store like a MySQL or NoSQL database, or from an external API.
  3. Once the data are received, the server can build the HTML. It generates the markup with React’s virtual DOM via the renderToString method.
  4. The server injects the data from step 2 into our HTML, allowing the browser to access it later.
  5. The server responds to the request with our fully built HTML.

Rendering in the Browser

Now let’s look more closely at what happens on the browser. Figure 7 shows the flow in the browser, from the point the browser receives the html to the point it bootstraps the app:

  1. The browser parses the DOM that it has received from the server;
  2. This results in rendering an HTML element; or
  3. Executing JavaScript;
  4. When the browser reaches our entry point for the application, the app bootstraps itself.

kolkergordon_aitiwaa_07

Figure 7 Browser Render & Bootstrap


At this point our single-page application flow kicks in again. This is the most straightforward part. It handles user events, makes XHR calls and updates the application as needed.

For more on building exciting isomorphic web apps, download the free first chapter of Isomorphic Development with JavaScript and see this Slideshare presentation for a discount code.