Skip to main content

In Pursuit of the Holy Grail: Building Isomorphic JavaScript Apps

Spike Brehm (Airbnb)
Front End Libraries, Node.js, The Leading Edge
Location: Salon 9 Level: Advanced
Average rating: **...
(2.80, 15 ratings)

Libraries like Backbone, Ember and Angular have led to an explosion in single-page web apps. These apps can provide UIs that feel faster and more interactive than traditional web apps, but this new hotness is not without its drawbacks.

The idealized single-page app runs exclusively in the browser, asking the server only for data. While this can lead to a nice, clean separation of concerns, inevitably some bits of application logic or view logic end up duplicated between client and server, often in different languages.

Worse still, an application that can only run in the client-side is not able to serve HTML to users or crawlers. This results in drastically worse initial page-load times, and more difficulty providing SEO.

At the end of the day, we really want a hybrid of the new and old approaches: we want to serve fully-formed HTML from the server for performance and SEO, but we want the speed and flexibility of client-side application logic.

Isomorphic JavaScript: The Holy Grail

Now that we have a fast, stable server-side JavaScript runtime in the form of Node.js, we can make this dream a reality. By creating the appropriate abstractions, we can write our application logic such that it runs on both the server and the client — the definition of isomorphic JavaScript.

In our experience building isomorphic JavaScript apps at Airbnb, we have found that the inherent challenges can be separated into two areas: Writing Isomorphic Application Code and Building and Bundling.

Writing Isomorphic Application Code

To allow our models, views, controllers and routes to execute in both environments, we need to create a set of abstractions that decouple our application logic from the underlying implementations.

Routing: We want a single set of routes that map URL patterns to route handlers. Our route handlers need to be able to access headers, cookies, and URL information, and specify redirects without directly accessing window.location or req and res.

Fetching data: We want to describe the resources needed to render a particular page or component independently from the fetching mechanism. The resource descriptor could be a simple URI pointing to a JSON endpoint, or for larger applications, it may be useful to encapsulate resources in models and collections and specify a model class and primary key, which at some point would get translated to a URI.

View rendering: Whether we choose to directly manipulate the DOM, stick with string-based HTML templating, or opt for a UI component library with a DOM abstraction, we need to be able to generate markup isomorphically.

Building And Bundling

As we’ve discovered, writing isomorphic application code is only half the battle. Tools like Grunt and Browserify are essential parts of the workflow to actually get the app up and running. There can be a number of build steps: compiling templates, including client-side dependencies, applying transforms, minification, etc.

The simple case is to combine all application code, views and templates into a single bundle, but for larger apps, this can result in hundreds of kilobytes to download. A more advanced approach is to create dynamic bundles and introduce asset lazy-loading, however this quickly gets complicated. Static-analysis tools like Esprima can allow ambitious developers to attempt advanced optimization.

Tools and Community

Finally, I’ll introduce some of the open source projects and tools that you can use today to build isomorphic JavaScript apps. For example, Rendr is an open-source library that we created at Airbnb to build isomorphic Backbone + Handlebars apps. Facebook uses React.js to render the same HTML on the client & server. Ember has released its routing layer in the form of route-recognizer, which should allow for sharing the same routes and route handlers on client & server. TJ Holowaychuk’s superagent module provides a single API for HTTP requests on the client & server.

Spike Brehm

Airbnb

Spike Brehm is a Sofware Engineer specializing in rich-client JavaScript apps at Airbnb. In over a decade of experience with JavaScript, he has seen its role on the web morph from novelty to basic building block.

He’s currently prototyping the next generation of Airbnb’s front end stack, and is busy open-sourcing Rendr: a library for building web applications that run on both sides of the wire, fetching data and rendering views on client and server, built on Backbone.js and Node.js.