Choosing Ember over React in 2016

Jesse Pollak
Two-Factor Everything
13 min readNov 14, 2016

--

One month ago, we started working on a new product: Instant 2FA, the easiest way to add two-factor authentication to any site in less than an hour.

Currently, whether you roll your own two-factor with Google Authenticator or use an API service like Authy, a standard integration often takes weeks.

With Instant 2FA, we’re building a solution that reduces the entire integration to 3 API calls and provides everything out of the box: Google Authenticator, SMS, and Yubikey support; remember this computer; rate-limiting and alerting; and with our first few customers, we’ve done the integration in a development environment in less than an hour.

This blog post outlines our thinking behind choosing Ember (a technology none of us had ever used before) over React (which we’ve been running for 2.5 years in production at Clef) and our reflections on the decision after one month of work.

Our experience

Every new project requires a million technology decisions. Luckily, 4 years of company experience building Clef, a two-factor product used by nearly a million websites, made most of our decisions obvious. We knew our backend services would be built with Python, SQL, and Flask, leveraging all the tooling and libraries we’d spent years working on.

The frontend, however, was a different story: we’ve been using React and flux (powered by reflux) in production for two and a half years, but as we began considering how to build Instant 2FA, we wondered whether using a more fully featured framework like Ember or Angular would be a better decision.

Evaluating ember

When we started considering Ember over React, there were two main hypotheses that drove our curiosity:

  1. Convention over configuration. Since Ember handles many things out of the box that React requires composition of other libraries to do, we would be able to accomplish more, faster, and without suffering from decision fatigue.
  2. Progressive enhancement & surviving the framework hype cycle. Since Ember has a very strong community that’s very committed to making Ember the best way to build modern web applications, we would be able to use the latest and greatest technologies, even if our use came a little later on the adoption curve (perhaps on the plateau of productivity).

As we dug into each of these hypotheses, we found compelling evidence that ultimately led us to choosing Ember for both our applications.

Convention over configuration

One of the best things about React is that, since it’s only a view layer, it can easily be added to a pre-existing JavaScript application. Three years ago, when the frontend for Clef was built on our own object oriented JavaScript framework and we started exploring whether React could help us, this was one of the best parts. Rather than swap in a new “framework,” we gradually replaced different components with React components — adding additional logic layers on top of React (like redux to handle data flow) only as necessary.

Over time, this gradual composition of libraries cemented into our own little incarnation of the React ecosystem. Different libraries duplicated each other, but things worked. We used:

  • coffeescript
  • React for the view
  • reflux for data flow
  • react-router 2 for routing
  • jQuery.ajax for network requests
  • underscore for utilities
  • browserify for module building
  • gulp for tasks
  • SASS for styles
  • no tests 😬

A few months ago, we did a little side project where we had to create a client application from scratch. We went with React for the view layer, but given the dramatic evolution of the surrounding ecosystem over the last few years, we ended up with a very different composition:

  • ES6
  • React for the view
  • redux and redux-saga for data flow
  • react-router 3 for routing
  • fetch for network requests
  • lodash for utilities
  • webpack for module building
  • post-css & css-modules for styles
  • karma, mocha, sinon and chai for tests

It was fun to get to pick and choose, but every step of the way we doubted our choices: were we making the wrong decision with a given library choice? We were “configuring” our own framework from the tools available in the React ecosystem, but had major concerns about whether our configuration would help or hurt us.

Entrusting high-level architectural technology decisions to engineers who have limited experience in a domain often leads to bikeshedding and bad decisions — and this showed in the side project we did. We are a team of 4 engineers building Clef, but since we regularly maintain code across 5 platforms (iOS, Android, backend Python, client JavaScript, and our WordPress plugin), only two of our engineers have really worked on our React application. By the end of working on the side project as a team, we’d saddled ourselves with a set of tools which often proved unstable and got in the way of getting our work done.

With Instant 2FA, every engineer would be working in JavaScript for half their time, so this meant more than half of the people writing code would be relatively inexperienced JavaScript engineers. This makeup — senior overall, but relatively inexperienced with front end engineering — made the issue of convention over configuration feel particularly relevant.

As we started working on Instant 2FA, the thought of Ember making all these decisions for us — enforcing a convention over configuration — was very alluring. Some of the decisions we were most excited about not making were:

Tests

Ember ships with testing built in and tests are auto-generated when new units (models, routes, services, serializers, etc) are created. For the last React application we built, we’d spent days setting up test harnesses, acceptance tests, and all of the other tools necessary to have a fast, easy-to-use test suite, and we still were constantly frustrated with our setup. Ember built acceptance and unit testing into the framework, which we hoped would make it easier for our code to be tested at every level.

Builds & deployment

Ember is powered by ember-cli, which handles every step of your build: transpiling JavaScript, building modules, productionizing code, and even deploying (with ember-cli-deploy). This toolchain comes with sane defaults out of the box (ES6 and modules, for instance), but the community built around this standard is the most powerful part.

In our previous setups, every time we needed to change our build to implement a specific goal, we needed to write custom code. One great example of this is uploading sourcemaps to Sentry when our code is deployed. In our React apps, we did this in a shell script after our webpack build was done.

With ember-cli, almost any new step we need to add is available as an Ember addon (like ember-cli-deploy-sentry). And, because of the way Ember enforces conventions, these addons almost always work out of the box. As a result our build and deploy logic has transformed from a mess of custom JavaScript and shell script logic to a clean composition of different community maintained modules.

Data flow & modeling

The default implementation of data flow and persistence in Ember relies on ember-data, which is maintained by the core team (along with ember-cli and Ember core). With our most recent React experience, when we used redux with redux-saga, while we felt at the cutting edge, we always seemed moments away from flushing days down the drain trying to figure out how to do something. When evaluating Ember, We were excited that with ember-data there were established patterns for how to do things — even if some of those patterns felt a little outdated.

Server side rendering.

Ember FastBoot enables server side rendering in any Ember app with one command. Having explored server side rendering in our previous React apps, we knew that for every configuration of libraries around React there was an equally complicated server side rendering setup. Server side rendering wasn’t something we needed out of the box, and we still haven’t enabled it, but knowing that it’s a cli command away, rather than a major project away, is a weight off our shoulders.

After outlining and exploring all the decisions we would need to make if we went with the React ecosystem, taking those decisions out of our hands, and putting them in the hands of a core team and community that has been building client-side applications for years, became an obvious choice.

Progressive enhancement

When the topic of progressive enhancement is discussed, it most often refers to the progressive enhancement of webpages from plain HTML & CSS to advanced, interactive JavaScript powered applications. The argument goes like this: for people browsing a web application, their experience should be progressively enhanced given the technologies their browser supports (or has enabled). A user with no JS support in their browser will get a plain HTML & CSS experience — which though it may be slower or clunkier, will still work — while a user with a modern browser will be served the JavaScript necessary to make their experience fully modern. In other words, with progressive enhancement, we meet users where they are — and, perhaps most importantly, they get the best experience available to them without having to do any work.

The best ideas of progressive enhancement should be evident in JavaScript frameworks and libraries as well — we refer to this a Progressive Developer Experience (Progressive DX) and it’s one of the defining goals of the Ember framework and community.

In the React ecosystem, progress most often happens through the creation of new libraries. The flux paradigm, and the concept of uni-directional data flow, is a particularly good example of this. When Facebook first published the flux architecture, a litany of small libraries that implemented the pattern sprang up (one of which was reflux, which we started using). Over time, many of these libraries died off, while communities formed around others (like redux), but the evolution of the concept most often happened through the introduction of new libraries. For instance, for handling asynchronous actions in redux, this has evolved from just handling the logic in redux to a slightly more involved layer like redux-thunk to using promises with redux-promise to the concept of sagas with redux-saga. For each new iteration of progress at the conceptual level, a new library or tool needs to be added to your toolset: or tying this back to the language of progressive enhancement, the developer has to do work or change their tools to get the best experience.

In contrast, one of the goals of Ember is to give the developer the best experience currently available without having them do any work (or as little as possible). Take the same example of uni-directional data flow. While Ember 1.0 existed before this pattern became well known, as the concept grew in popularity and conceptual clarity in the React & flux ecosystem the Ember core team started figuring out how they could roll it back into Ember. With Ember 2.0, and the gradual shift from controllers to components, uni-directional data flow has found a home in Ember without Ember developers needing to add new libraries or adopt major conceptual paradigms. It may have taken a little longer to get to this enhancement — but when it came it was well thought out, built into the framework they already used, and there was a straightforward migration path. This is a perfect example of how Ember employs Progressive DX to give its developers access to the latest patterns and tools!

Reading the core team's work during our evaluation period, this theme of Progressive DX felt consistent: features like Ember Fastboot (server side rendering), glimmer (faster rendering enginer), routable components (uni-directional data flow), and services (isolated cross cutting concerns) are all examples of how the Ember experience has been progressively enhanced, often borrowing directly from patterns other frameworks have introduced, to give Ember users the best of the web.

At the beginning of a project that will last for years, it's comforting to know that we have a team working to integrate the latest and greatest into the tools we already use, so we don't have to search for it ourselves.

Reflections one month in

One month in, we’re very happy with our decision to use Ember. It’s still early, but overall, our two hypotheses have held — we’re enjoying following conventions defined by more senior engineers and are already benefiting from different ways Ember has progressively enhanced our experience.

The solid documentation makes getting started with the framework pretty easy and having a bunch of conventions already determined is freeing.
- Grace Wong, Engineer

Ember is sweet. Really easy to pick up enough to design well, and I really love using css-modules.
- Aayush Iyer, Designer

We’ve also found a ton of other, smaller reasons to love Ember.

Some more good parts

ember generate

Having never used a framework that provided command line tools for generating new units of logic, we were skeptical of the value ember-cli would provide with the ember generate command. After working with the framework for a little more than a month, it's one of our favorite things: having a canonical way to generate any unit of logic (model, route, service, etc), means there's no cognitive overhead to writing many, small, easily testable units. It also means that whenever a unit is created, a test is auto-generated for it, so developers have an even easier time testing their code.

ember-cli-mirage

Mirage is a ember-cli plugin that makes it dead simple to mock AJAX requests and responses in development. This has proven invaluable for building new features on a team. Instead of waiting for a teammate to get server routes up, or context switching to do it ourselves, we can create routes and seed the data we need with just a few lines. Combined with ember-data, it's even easy to generate complex relationship setups, which has historically been a challenge in our development workflows.

Loading and error substates

Adding loading and error states to views is a pattern that every frontend engineer has had to implement multiple times. With Ember, for route loading, loading and error states are automatically handled: given a route hierarchy (for instance /organization/<id>/applications), Ember will look for a loading.hbs and error.hbs template in every template folder. When data is loading, or loading fails, Ember will automatically render the respective loading or error template closest to the route the user is currently on. This lets you easily add a top-level loading state, then add specific loading states throughout your app where necessary. It's beautiful!

Some not so good parts

Of course everything hasn’t been perfect — learning any new framework is a challenge and migrating from one framework to another always brings some amount of frustration with the different paradigms.

No hot-reloading or time travel debugging

Hot-reloading and time travel debugging are two of the most useful tools that have come out of the React ecosystem.

With hot-reloading, when you change code, the logic is reloaded in place, without requiring a page load. As a developer, this means you don’t need to navigate back to the point in the page — with the appropriate state — where you were working every time you make a change. Instead, the logic just refreshes and you can keep on working.

With time travel debugging, it’s trivial to see how state in your application changes over time, and jump through those different states. This makes it easy to diagnose and fix bugs that come from complicated state manipulation!

While there is a project to do hot reloading in Ember, it’s very young and has limited support for different parts of an Ember application. Similarly, time travel debugging isn’t a concept that’s gotten traction in the Ember community — partially because data flow in Ember isn’t as well suited to it as a system like redux.

Not having access to these tools is sad, but not the end of the world.

Styling components is still the wild west

In our React applications, styling components was always a point of frustration. This boiled down to two primary issues:

  1. There wasn’t a defined best-practice for how to style components. css-modules? CSS in JS? plain SASS?
  2. Mostly because of (1), there wasn’t a defined best-practice for how to style components in a way where they could be published to NPM and then easily consumed in other projects

In the Ember eco-system, (1) seems similarly unsolved: we’ve gone with css-modules for styling components, but have had to do contortions to make it work with a base styling system (like foundation). We haven’t had to do (2) yet, but it seems much simpler in Ember with Ember addons.

Conclusion

This post isn’t about whether Ember is better than React. This post is about why, when Clef was deciding whether to use React or Ember to build Instant 2FA, we went with Ember.

There are an infinite number of ways to build a web application and there are an infinite number of reasons the right decisions for our needs could be the wrong ones for you. This diversity of options is what makes the web evolve so fast — and what makes it so powerful.

We hope you learned something from our thought process — and we’d love to learn something from yours. If you’ve made the same choice, we’d love to hear why! If you’ve made a different one, we’d love to hear why too!

Post a response or tweet at me at @jessepollak to continue the conversation.

Many thanks to Chris and Trek Glowacki for reading the first draft of this and mixonic for inventing the Progressive DX terminology.

If 2FA has been in your backlog for months, check out Instant 2FA, the easiest way to add two-factor authentication to any site in minutes.

--

--

Jesse Pollak
Two-Factor Everything

building @coinbase; previously @getclef and @instant2fa.