Turadg Aleahmad
6 min readAug 18, 2015

Why and How Coursera Does Isomorphic Javascript:
A Fast and Snappy Quiz

Pop quiz! Have no fear; this will be the easiest Coursera quiz you ever take.

Coursera’s home page is rendered by…

  • a) your browser
  • b) a server
  • c) a server and also your browser by the same code.
  • d) microscopic monkeys squeezing the liquid crystals in your display.

If you answered C, you are correct! Coursera has adopted isomorphic rendering.

“Isomorphic” rendering means…

  • a) your UI feels snappy, like a good Single Page App.
  • b) your UI renders fast, before all the SPA assets are downloaded.
  • c) both “a” and b”.
  • d) the same as Universal Javascript.

If you answered C, you are correct! (Partial credit for D). Back in the day, web pages were all rendered on the server and the web browser simply turned the HTML into pixels. Web browsers have come a long way since Mosaic and are now little operating systems. Web pages are little applications that run in them. This has allowed more of the logic of a web application to run in the client instead of the server, and for UIs to be much snappier. But it has also meant:

SPAs take longer to become interactive because of all the assets that have to be downloaded to the client. (Forming a cottage industry of spinners.) What you really want is to have the page arrive fast like in the old days, and then to be snappy once all the client logic is running. Isomorphic rendering buys this the best of both worlds.

Why did Coursera adopt isomorphic rendering?

  • a) We adopt any new shiny tech.
  • b) Our servers were too idle.
  • c) Many of our learners around the globe have relatively poor network connectivity.
  • d) Fridays our engineers play Truth or Dare and there were no questions left for Truth because we are such a transparent company.

If you answered C, you are correct! Coursera’s mission is to create universal access to the world’s best education. Moreover learners with slow networks are often those must hungry for education. We must reach low bandwidth, high latency environments like mobile networks in India and eventually over drone link in rural Africa. In extreme cases, we saw some users in these environments wait minutes for all the Javascript to load before they could see even the page. We knew other potential learners with poor connections must be simply giving up. In controlled experiments, we found a drop in usage around 8s page load time. With isomorphic rendering, we were able to meet this threshold even in many of these extreme cases.

With server rendering, learners with the 10% slowest connections saw…

  • a) Coursera pages 2x sooner.
  • b) Coursera pages 3x sooner.
  • c) Coursera pages 4x sooner.
  • d) Coursera before they even requested it.

If you answered C, you are correct! At the median of our time-to-interactivity metric, our pages reached learners twice as fast. Learners at the 90th percentile received our pages four times as fast as before. And for the 10% with even slower connections, the gains were even greater.

How

So how does it work? Isomorphic rendering places new demands on development. We share some details on:

  • application architecture
  • serving infrastructure
  • development

There is also a considerable cost to migrating existing applications, some of which we describe in How To Migrate React to Isomorphic Rendering.

Application architecture

Coursera’s isomorphic applications are in…

  • a) Backbone
  • b) Meteor
  • c) React
  • d) ScalaJS

If you answered C, you are correct! Note that there are many ways to go isomorphic. We use and recommend React with a Flux application architecture. We’ve been gradually migrating from Backbone to React over the past year and it has provided marked improvements in our developer experience and velocity. We had already adopted React so the cost here was only added costs for isomorphic rendering were to manage rehydration. Here’s how it works:

React rehydration sequence

Serving infrastructure

At Coursera backend services are almost exclusively in Scala. Our deployment and monitoring systems are optimized for our Scala services, but a Java VM (on which Scala runs) is not a great way to run Javascript. Instead we have a Render service that manages a cluster of Node co-processes that do the Javascript rendering. Our existing Edge service handles all browser requests and, based on its routing table, passes along those that should be server-rendered. If any requests fail, Edge automatically falls back to client-side rendering.

Coursera’s Render service

We may write another article with details on Edge, Render, and the Node co-process. If you are interested, please let us know @CourseraEng.

Development

Once you’ve got the rendering working, you’re all set, right? Actually, there are still a number of things to keep in mind in your app development. Here we describe a few.

User actions before rehydration

In your client-side rendered SPA, the user couldn’t do anything until all the Javascript was running. Now she sees the page before the Javascript assets arrive and she expects to start using it. What do you do?

WHAT DO YOU DO?!

  • a) Go Web 1.0, <form> tags, server handling, all that.
  • b) Limit interactions until rehydration.
  • c) A or B on a case-by-case basis.
  • d) Insert <script>
    Array.prototype.slice.call(document.querySelectorAll(‘*’)).forEach(function(node) {node.onclick = function(){window.alert(‘RELAX! PAGE NOT LOADED YET.’)};});
    </script>

If you answered C, you are correct! Some actions you never want to make a user wait for. Signing up is a good example. “Oh I want to sign up to learn with others and works towards a better life but there’s this big spinner. Nevermind, off to cat videos.” Because this happens (in our data 37.337% of the time) we replaced our JSON auth APIs with good old Web 1.0 x-www-form-urlencoded form POSTs. Thanks to HTML5 we were able to use the browser’s native form validation without waiting for Javascript. Of course sometimes there’s no way around running Javascript, such as a nav menu. In these cases you might want to insert a little bit of Javascript on the page to handle each case. But be careful because these are hacks out of your React flow.

Event tracking before rehydration

Whatever your users do before rehydration, you don’t want to lose those events. If you fail to event before rehydration then you’re introducing sample bias into all your analysis, differentially excluding events from people with slower internet.

We solve this with a library we call Retracked that we’ve open-sourced at https://github.com/coursera/retracked. To track clicks, we put anchor tags on the page, but instead of a React-Router Link, we use Retracked’s TrackedLink which augments the <a> tag to record events the same pre- and post- rehydration. To activate it, a separate snippet of Javascript listens to all TrackedLink anchors on the page and records clicks. Once rehydration happens it lets React’s event handler take over.

Operations

Your code is flawless, but sometimes other developers you work with may cause problems. To minimize the impact on users we have unit tests, integration tests, and continual monitoring. Our unit tests ensure that our basic server-side rendering code path works in our testing environment. Once code is landed on master, we have Selenium tests on staging asset builds that verify the SSR is working with production services. Because our Edge service is robust to server errors, we test not just that the page renders but that it rendered on the server before the client. All that takes is testing for the presence of the data-react-checksum attribute produced by renderToString(). Finally, our monitoring systems tracks “time-to-interactivity”, the time it takes a user from navigating until they the page is useful to them. Using the Navigation Timing API, tti = (timing.domInteractive — timing.navigationStart).

Take-aways

  • Isomorphic rendering can dramatically improve your users’ experience
  • Developing apps that run SSR introduces some enduring complications
  • You might love working at Coursera.

Thanks to Richard Wong, Brennan Saeta, Nick Dellamaggiore, and Joshua Newman for reading drafts of this.

Subscribe to Building Coursera for more, like our article detailing problems we had to solve while migrating.