At Airbnb, we have learned a lot over the past few years, creating powerful web applications. We plunged into the world of one-page applications in 2011, doing a
mobile version of our site , since then, among other things, we launched
Wish Lists and a new
search . These are all large JavaScript applications, which means that tons of code are launched in the browser to provide a modern, interactive user experience.
This is the usual approach today when libraries such as backbone.js, ember.js and angular.js help developers create powerful JavaScript applications. We realized, however, that such applications have several critical limitations. To make it clear, let's take a little tour of the history of web applications.
Picture from the article to attract attentionJavascript matures
At the dawn, Web interaction with browsers was like this: a web browser requested each page (for example,
www.geocities.com ) forcing a server somewhere on the Internet to generate HTML and send it back. This was a good approach, since browsers were not particularly powerful at that time, and the HTML pages were mostly static and autonomous. JavaScript, created to allow web pages to be more dynamic, did not allow anything more complex than a slideshow or a date picker.
')
After several years of computer technology, enthusiastic developers pushed the web beyond its limitations, forcing it to evolve. Now the web is so developed that it has become a full-fledged platform for applications - and fast JavaScript and html5 standard allow developers to create powerful applications that could only be done under a specific platform.
Single Page Applications
After some time, developers began to create entire applications in the browser, using JavaScript and new features. Applications like Gmail (a classic example of a one-page application) can instantly react to user actions, no longer need to run full circle to the server, just to display a new page.
Libraries like backbone.js, ember.js, and angular.js are often referred to as client MVC or MVVM libraries. The classic MVC client architecture looks like this:

A large amount of application logic (views, templates, controllers, models, internationalization, etc.) lives on the client, a special API is used for the data. The server can be written in any language, such as Ruby, Python or Java. In most cases, their role is to give away a frame page. As soon as the JavaScript files are loaded by the browser, they are executed, the client application is initialized, the data is loaded via the corresponding API, and the rest of the HTML is drawn.
This is good for users, because once loaded, the application can quickly switch between pages without reloading, and, if done correctly, even work offline.
This is also good for developers, as the ideal one-page application has a clear separation of tasks into client and server, which provides an excellent development process and prevents the need to duplicate too much logic between two environments, which are also often written in different languages.
Trouble in Paradise
In practice, however, this approach has several fatal flaws that prevent it from being used in many cases.
Search engine optimization
An application that works only on the client cannot interact normally with search robots, that is, it is ill suited for SEO by default. Search robots work by creating requests to the web server and interpreting the results, but if the server returns only a blank page, this is of little use. There are of course and workarounds, but not without dancing with a tambourine.
Performance
For the same reason, if the server does not generate the entire page, and instead have to wait for JavaScript to load on the client, users see a blank page or download icon for a few critical seconds. A lot of research has been conducted studying the effect of slow websites on users, and, accordingly, on profits.
Amazon claims that every 100 ms load reduction increases profits by 1%. Twitter spent a year of working together 40 developers reworking the site to
generate pages entirely on the server instead of the client , receiving a 5-fold improvement in perceived load time.
Support
This should ideally be a nice, well-separated code in life; t inevitably, part of the application logic or display logic is duplicated between the client and the server, often written in different languages. The standard example is date or currency formatting, form validation and routing logic. All this makes support a nightmare, especially in the case of complex applications.
Some developers, including me, are lumping here - often only after you have spent a lot of time and effort creating a one-page application - these flaws float out.
Hybrid approach
As a result, we need a hybrid of new and old approaches: we want to get fully formed HTML from the server for reasons of performance and SEO, but we also need the speed and flexibility of the client-side applications.
To achieve this, we, in Airbnb, are experimenting with “isomorphic JavaScript applications”, JavaScript applications that can work both on the client and on the server.
An isomorphic application looks like this. Captioned “Client Server MVC”:

Here, part of the application or mapping logic can be executed both on the server and on the client. This provides many options: runtime optimization, better support, default SEO, and more controlled web applications.
C
node.js is a fast, stable server-side JavaScript part, we can finally turn this dream into a reality. Having created the necessary abstractions, we can write the logic of our application such that it runs both on the server and on the client — this is isomorphic JavaScript.
Isomorphic javascript in the wild
The idea is not new - Nodejitsu made a
good description of the architecture of isomorphic JavaScript back in 2011 - but it was difficult to adapt. Since then, several more isomorphic frameworks have emerged.
Mojito was the first known open source isomorphic framework. It is an advanced, full-stack framework based on node.js, but it depended on yui, and yahoo fads did not contribute to its particular popularity in the JavaScript community since it
became open in April 2012.
Meteor is probably the most widely known isomorphic project today. Meteor was created from scratch to support real-time applications, and its team created a whole ecosystem around its batch system and deployment tools. Like Mojito, this is a large, overconfident node.js framework. However, a lot has been done to attract the JavaScript community, and their most anticipated release 1.0 is just around the corner (approx. Trans. In early 2014). Meteor is a project to watch out for - a star team, $ 11.2 million financing from Andreessen Horowitz is unheard of for a company that is fully focused on the release of an open source product.
Asana , a Facebook based task management app with co-founder Dustin Moskowitz, has an interesting isomorphic history. Without having problems with financing, considering the status of Moskowitz as the youngest billionaire in the world, Asana conducted years of research, developing her own closed source code
Luna - one of the most advanced examples of isomorphic JavaScript. Luna, originally created on
v8cgi (in the days preceding node.js), allowed to run a copy of the application on the server for each user session. For each user, a separate server process was launched, executing the same JavaScript code running on the client. This gave all the advantages of advanced optimization, such as offline resistance and fast response.
We also created our isomorphic library this year. It is called
Rendr . It allows you to create backbone.js + handlebars.js one-page applications that can also completely form pages on the server. Rendr is the result of our experience reworking the
mobile version of Airbnb to improve download speed, which is especially important for mobile users who have high data transfer delays. Rendr is a library rather than a framework that solves several problems (if you compare it with Mojito or Meteor), but it is easy to change and expand.
Abstractions, abstractions, abstractions
All of these projects are quite large - which means that full-stack frameworks face rather difficult problems. The client and server are quite different environments, so you need to create a specific set of abstractions that will separate the application logic from the underlying implementations, so that you can create a single API for application developers ...
Routing
We need a single set of routes that match certain URI patterns to the appropriate handlers. These handlers should have access to: HTTP headers, cookies, information from URIs and the possibility of redirection without direct access to windows.location (in the browser) and req and res (in node.js).
Loading and saving data
It is necessary to somehow describe the resources needed to render specific pages or components, regardless of loading mechanisms. The resource description must be a simple URI pointing to a specific JSON, or, for large applications, it may be useful to encapsulate resources into models and collections and determine the model class and primary key that would somehow be translated into a URI.
Page generation
Are we going to change the DOM manually, or using HTML template engines, or using any component library that abstracts from the DOM, in any case, we need to be able to generate markup isomorphically. It should be possible to generate any presentation on both the server and the client, depending on the needs of the application.
Assembly and packaging
It turns out that writing isomorphic application code is only half the battle. Tools such as
grunt and
browserify are an integral part of creating and running applications. Assembly usually requires several steps: compiling templates, including client dependencies, applying transformations, minifying, etc. In the simplest case, you need to collect all the application code, presentation and templates into a single package, but for large applications, this may turn into the need to transfer hundreds of kilobytes to the client. A more advanced approach is to create a dynamic bundle and provide the possibility of deferred loading, but in this case, the complexity quickly increases. Statistical code analysis tools such as
Esprima allow aspiring developers to perform advanced optimization and
metaprogramming to reduce the amount of template code.
We assemble from small modules
Entering the market with the first isomorphic framework means that you managed to solve all these problems. But the result may be a huge, unwieldy framework that will be difficult to adapt and integrate into existing applications. As more and more developers are connecting to solving these problems, we will soon face the emergence of a large number of small, reusable modules that can be put together to create isomorphic applications.
Obviously, most JavaScript modules can already be used isomorphically, either without or with minor modifications. For example, such popular libraries as undrscore, backbone, handlebars, moment and even jquery can already be used on the server.
To demonstrate all this, I created a small application called an
isomorphic tutorial that you can take from GitHub. By combining several modules together, each of which can be used isomorphically, it is quite simple to create a small isomorphic application in a hundred lines of code. Here are used:
Director for server and browser routing,
Superagent for HTTP requests and
handlebars for templating, all this is built on top of the usual
express.js application. Of course, as the complexity of the application grows, you will need to add additional layers of abstraction, but I hope that the more developers experiment with this approach, the more new libraries and standards will appear.
Look around
The more organizations will run node.js in production, the more relentless it will be that more and more code will be shared between the client and the server. It is important to remember that isomorphic JavaScript can be used both locally for narrow and wide tasks — you can start by separating the templates, moving on to separating the entire display subsystem, and completing the business logic of the application with a complete separation. The specific way in which JavaScript is divided between environments depends entirely on how the application is created and on its features.
Nicholas C. Zakas perfectly
described how, in his opinion, applications will transition the UI layer from client to server, giving a performance gain and better support. To use isomorphic JavaScript, you will not need to abandon the existing backend and replace its node.js, throwing out the child with water. Instead, by creating the right API and using the RESTful approach, the traditional backend can live peacefully next to node.js.
In Airbnb, we have already begun to redo the process of creating the client side using node-based tools such as grunt and browserify. Our main rails application can never be completely displaced by node.js, but using them together makes it even easier to share certain parts of JavaScript between environments.