An isomorphic request, server side
In an isomorphic application, the first request made by the web browser is processed by the server while subsequent requests are processed by the client. We will use the following article as an example:
When we inspect the source code (in Chrome, by hitting Ctrl + u) in the Network tab, we see HTML, CSS and images:
The asset /build/bundle.js is the React web application, which contains the list of routes, templates and components required to navigate through the site. We will see further down how /build/bundle.js assumes command of the navigation through the website after the first request. Here is a step-by-step list of how this first request was processed:
- We entered https://www.lullabot.com/articles/importing-huge-databases-faster in the browser and hit Enter.
- The request reached the web server and was passed to a Node.js application.
- Node.js passed the request to React, which fetched the article’s data from CouchDB, built the full page, and returned it to Node.js.
- The user will see the response in HTML, while the browser downloads the React application (/build/bundle.js) asynchronously.
Here is the trick: this same request is processed differently if instead of entering the article in the address bar and hitting Enter, we navigate to the list of articles and then click on the article’s link. Let’s try it out.
An isomorphic request, client side
Now that we have the article (and the application) loaded in the browser, let’s navigate to the list of articles and click on the same article we accessed in the previous section. Here is the link to the article that we will click:
And here is the response viewed using Chrome’s Developer Tools to inspect the network activity. Notice the XHR requests at the log, which use the XMLHttpRequest object:
Here is how this request was processed:
- We clicked on the article link at /articles.
- React intercepted the click event and stopped propagation.
- React issued an XMLHttpRequest to CouchDB to fetch the article.
- React passed the article data to the article template and rendered it.
The main difference between this request and the one in the previous section is that this time the request did not hit our Node.js application. This is fast, efficient and made my jaw drop the first time I saw it.
Comparing server/client requests
The following screenshot summarizes the above interactions by comparing them side by side:
Notice how the network activity log varies from one to another. For the one in the left we entered https://www.lullabot.com/articles/importing-huge-databases-faster in the address field of our browser and hit Enter; while in the other instance we clicked on the article listed at https://www.lullabot.com/articles. The end result is identical for the user, but using the server-side method (which is the common way of doing things today) took 40x as long for the basic page load, as using client-side processing.
Benefits of going isomorphic
- The first page request is fast and subsequent ones are even faster; as opposed to common SPAs, where the first request is used to load the application and then a round trip is made to fetch what was requested.
- There is less code, as it is shared by both the client and the server.
Risks of isomorphic applications
There is a considerable learning curve when building an isomorphic application for the first time. In our case, when we rebuilt Lullabot.com, only Jeff Eaton and Sally Young were familiar with how isomorphic applications worked. The rest of us had to learn along the way and—while it was a mind-blowing experience—that can be frustrating as well.
Here are some of the challenges where I found myself banging my head against the wall:
Debugging is trickier
Bye bye JQuery
We decided not to use JQuery as when React runs in the server there is no DOM to manipulate. For cases where we had to process Rich Text we used Cheerio, a leaner alternative. This made me realize how dependent I was on JQuery when I struggled to write a POST request using XMLHttpRequest instead of
Taking into account where the code would run
I had to be aware at all times whether the code that I was writing would run just in the server or also on the client because the client doesn't have access to the file system, which excludes many Node.js popular modules from being used.
Avoid exposing sensitive data
Every module required via Browserify would end up being part of the web application (build/bundle.js). This meant that we had to be cautious on which modules to require client side. For example, I almost exposed our MailChimp API keys by mistake while writing the Newsletter component.
We had to manage two sets of settings: server side settings (such as API keys and other credentials) and client side settings (such as the hostname of CouchDB, ElasticSearch and other resources). Some of these settings vary in local, development and production environments and we were not sure of how to make it easy to manage that. We found a solution by mixing the envify and dotenv modules.
There are advantages and risks when writing isomorphic applications. The technology is certainly exciting and I encourage you to try any of the available isomorphic libraries and see where it fits best in your scenario. Then, feel free to post your thoughts in a comment below.