It was crucial that our application returned server-rendered HTML that search engines knew how to understand, but many of the advantages of a "modern" approach were predicated on client-side rendering. We needed to be able to render the same codebase in both places.
Differences Between Client and Server Rendering
There are two primary obstacles that must be overcome to render the same app in both environments: titles, and data.
Server-side and client-side apps use different APIs to set the document's title. Keeping with the React tradition, Ambidex solves this with a declarative approach: each component can either statically or dynamically declare a
sectionTitle. When the page renders, Ambidex walks the component tree, combining the
sectionTitles and calling the environment's title API with the resulting value.
The data-loading problem is a bit trickier: The client should render immediately on every change to make sure the app feels responsive; however, the server only gets to respond once, so it must delay rendering until all the requisite data has loaded. Therefore, Ambidex needs to know which data each page depends on and to detect when that data is available.
To solve this problem, I first made a key insight: a search engine indexes its catalog by URL. Therefore, any data that's both needed to render a public page and changes between pages must be captured in the URL. By mapping each URL parameter to its corresponding Flux store and detecting when those stores have acceptable values, Ambidex can detect when the page is ready to render.
Since Ambidex was the tool I worked with every day, it was crucial for it to have a stellar developer experience.
One of the best features of Webpack is hot module replacement, which shows your changes immediately in every connected browser on each save. Unfortunately when I created Ambidex, HMR was very convoluted to set up and therefore not commonly used. To remedy this, the first thing I did when I started building Ambidex was make sure that enabling hot module replacement with Ambidex was as simple as setting a flag to
The store that I built Ambidex to power was designed to be viewed on a mobile phone, and agnostic to which data provider a merchant chose. To those ends, I created a test harness that ran three phone-sized instances of the webstore side-by-side in a single tab. Each was backend by a different data source. As I worked, I kept this test harness open in a tablet that sat at my desk. Every time I hit save, each of the test instances would instantly update with my changes. Because they were on a tablet, testing the touch interactions was painless. By backing each test with a different data provider, I ensured that we always maintained compatibility with our supported partners without any extra work on my part.
Ambidex provided the best workflow I've ever used. I shared my experience at Facebook's inaugural React.js Conference. You can watch my talk in the video player at the top of this page.