Every minute of every day, an average 52,000 people are searching Airbnb’s website for a place to stay.
The world’s largest home-sharing service supports 75 million searches each day — users looking to book one of the 3 million residences (including 3,000 castles and 1,400 tree houses) that Airbnb brokers in 191 countries around the globe. Since the company’s founding in 2008, the site has facilitated 160 million guest arrivals at listed properties.
That robust traffic is only increasing as Airbnb moves into new service areas. The company recently introduced Airbnb Trips, a feature allowing users to research and book local adventures along with their lodging. Airbnb Experiences connects Airbnb users with local resident experts who lead expeditions to go, say, truffle hunting in Italy or surf secret stretches of the California coast. Airbnb Places offers insider recommendations for destination cities, from general (the usual restaurants, bars, attractions, etc.) to highly idiosyncratic (secret spots for salsa dancing). Additional travel services, such as flight booking, are in the works and coming to the site soon.
As they contemplated building out the new Trips platform, Airbnb’s engineering team realized this was an opportunity to fundamentally re-architect a frontend codebase that began life in 2008 as a humble Rails/Prototype.js app. That still-serviceable but rapidly aging infrastructure followed a legacy approach of a landing page to search results to single listing to booking — each page delivered standalone via Rails. Very 2008 indeed.
Airbnb wanted the UX to be completely fluid, adjusting organically and even pre-emptively by the split second as users explore and narrow their search. “One driver of the new architecture was that the new product functionality we wanted on the client was simply not possible with the legacy stack,” Neary said, in a follow-up interview. For now, page requests are still handled via Rails, though even that may change in the future. A crucial step, however, was added: having a node service running the Hypernova library to actually render the React components serverside.
Imagine Rails fielding a request and retrieving the data required to render the page. Rails then passes that data to Hypernova, which returns the desired markup, and Rails forwards the page along to the user, where client-side code takes over.
Can’t We All Get Along?
The original Airbnb site had the luxury of server-rendered Rails pages, which could handle data thrown any-which-way at their server-rendered React components. “Controllers, helpers and presenters can produce data of any shape, and each component can consume whatever data is required even as you migrate page sections to React,” said Neary. The new client-side route, however, would require dynamic data delivered in a highly specific — “canonical” — form.
Eventually, said, Neary, the team would like to “look at something like GraphQL” for this functionality, but at the time of the original refactoring, it was decided to create a new version of the current API while requiring all components to begin consuming the canonical data shape. Migrating the existing server-side plumbing was actually fairly straightforward, said Neary: engineers “stepped through every place Rails was rendering a React component to ensure data inputs were the correct API shape, further validating compliance via React PropTypes on the client.”
More complicated than aligning the API was aligning the company’s multiple teams, each focused on a different aspect of the booking flow and spread out across Airbnb’s 19 global offices. “We needed to reach and re-educate all these folks that, even though it was still technically possible to pass data directly to the component being rendered, all data now needs to go through the API,” said Neary.
A separate bolus of (non-API) data also needed reworking. The functions themselves — application config, internationalization and localization, among others — were solid, but their frontend delivery mechanisms were “a bit under-baked.” Hypernova was in place to server-render React, but with some potential pitfalls. “If the server and client output don’t match to the bit, the page not only flashes the diff but also re-renders the entire page after load, which is terrible for performance,” Neary wrote in his blog summary of the process. He elaborated further:
“Worse yet, we had some magical Rails functions written long ago, for instance, add_bootstrap_data(key, value), which could ostensibly be called anywhere in Rails to make data available on the client globally via BootstrapData.get(key). What began as a helpful utility for a small team became a source of untraceable witchcraft for a large application and team. The “data laundering” crimes became increasingly tricky to unwind, as each team owns a different page or feature, and therefore each team cultivated a different mechanism for loading config, each suiting their unique needs.”
To exorcise the demons, the engineering team devised a specific mechanism for bootstrapping non-API data, directing all pages to this handoff between Rails and React/Hypernova. This higher order component received the necessary shape of bootstrap data as a JS object and identically initialized all the supporting tooling correctly on both client and server sides.
“In a single shot, we were able to eliminate add_bootstrap_data and prevented arbitrary keys passing through to top level React components,” said Neary.
Now for the Frontend
Early on the Airbnb site had rendered an entire page at a go, a practice that carried over into the company’s early stages with React. Now, however, an AsyncComponent splits off bundles from the component hierarchy to asynchronously load after mount. Transitions between routes are not only now “smooth as butter” but also an impressive step change (approximately five times) faster, said Neary. A necessary scenario for the ambitious animations the new Airbnb site was being designed to offer.
“Here we simply swap out the synchronous version of our map for an async version, which is particularly useful on a small breakpoint, where the map is displayed via user interaction with a button,” said Neary. “Since most of these users are on phones, getting them to interactive before worrying about Google Maps means a tasty boost in page load time.”
The Rest of the (Never Ending) Frontend Story
The Airbnb engineering team also tackled other issues during the overhaul as well, including accessibility. The internal component library is now being built with accessibility enforced as a hard constraint and, said Neary, “In the coming months we will have replaced all UI across the guest flow to be compatible with screen readers.”
A deeper dive into details (and code snippets!) about accessibility, how the team confronted handling the app state, and more are available in Neary’s blog post describing the process.
Airbnb managed an impressive stack reconfiguration in a short time with no disruption to users — while simultaneously rolling out significant new functionality. (Neary’s summary of this achievement — “Rebuilding a page serving 75 million searches per day is tricky” — is the very definition of the word “understatement.”) How did Neary and team achieve this impressive outcome? And, looking back, did he have any advice for other engineers contemplating a similarly daunting scenario?
The approach, according to Neary: first, hand pick a small group of engineers to work closely together on a short deadline to build the core functionality for the new architecture. Stable demo in hand, begin seeking feedback on the design and expand the team so as to rapidly iterate changes. Next, get a live version of the site running alongside the legacy version, and begin testing with external users.
“One of the best parts of this experience was that we were able to stay targeted, focusing in on a scope of work we could get live the same quarter in which we broke ground,” said Neary. Anything larger in scope, and the two branches of code would have started to diverge too dramatically, forcing teams to build new features for both. Staying targeted meant making the process easier not just for the dev team, but for the broader community of stakeholders involved.
Neary’s ultimate advice: “Do something bold, but then ship it. Long arcs are much harder.”