Having optimal web performance is crucial for platforms with more complex UIs, such as Airbnb. Airbnb’s web page consists of many larger components that can easily affect the page speed negatively, such as images and maps.
- Time To Interactive (TTI) decreased by 66% on client navigations by asynchronously loading components and sections, and sped up TTI for server navigations by 20-40%
- Progressive Hydration and Loading on Interaction works around the uncanny valley
PageSlot components. These components makes it possible to split each page into multiple sections, and defer downloading the code and rendering/hydration of these components if they are not in the viewport.
The React app is server-rendered using their own express-based isomorphic server, after which the components in each section get hydrated incrementally by PageSlot. Progressively hydrating the sections makes sure the most important components of the page are fully loaded and interactive, while deferring the loading and rendering of the sections that aren’t currently in the viewport.
A scheduler decides a section’s priority based on their position in relation to the current viewport.
- Highest priority: in the viewport with height that’s 0.
- High priority: in the viewport with a height that’s not 0.
- Low priority: the default priority, outside the viewport.
A component’s priority can be boosted by user interaction, for example by making a component visible within the viewport by scrolling down the page.
The same system handles client side navigation, rendering the page in sequence to simulate streaming content from the server, ensuring responsive page transitions.
Loading the page with the asynchronous PageSlot components has reduced the initial rendering time significantly, as the tasks have been split into multiple smaller tasks.
Airbnb’s server renders get interactive content to the user up to 20% faster with this approach, and up to 40% faster with a warm cache of JS bundles on the client.
Route-Based Code Splitting
To further improve the page’s performance, Airbnb uses both route-based and component-based code splitting.
Route-based code splitting ensures that only the content that is required for the current route will be fetched. Since unnecessary data and code won’t be loaded on the initial request, a smaller amount of data has to be processed, which results in a faster initial load and TTI.
Section-Based Code Splitting
Although route-based code splitting already reduces the amount of data and code requested, component-based code splitting can improve the loading experience even more. Components that aren’t necessary for the initial render, such as components further down the page or components that are only visible on user interaction, can be deferred.
To support component level async, Airbnb splits up code along the same section boundaries used for rendering through PageSlot, as depicted below. PageSlot’s viewport based scheduler ensures that even though the code is loading async, the content renders sequentially to avoid large layout shifts and content moving unpredictably.
Within sections Airbnb further uses a React.lazy like system for progressive enhancement of non-critical content after hydration, for example the favorite hearts within a photo carousel.
Code for interactive elements that aren’t initially visible, such as modals and panels, are loaded on user interaction as well. For example the
AsyncModal component loads the modal content when the user causes the modal to become visible. For common modals an idle task is used to prefetch the code after rendering higher priority content to reduce the delay when opening the modal.
Behind the scenes, Airbnb starts a service worker as soon as the contents of the page have been loaded. The service worker prefetches the resources that are necessary for the next steps that the user is most likely to take based on the current route.
Besides lazy loading components, Airbnb also lazy loads images that aren’t initially visible to the user. Images within an image carousel are prefetched based on the current slide. The image transitions are seamless, as the images that are further down the carousel have already been prefetched.
Wherever possible, Airbnb serves images with the WebP format. This modern image format reduces the image size by 25-34% compared to JPEG. (source).
Responsive Images Airbnb serves responsive images with adaptive quality compression based on the client characteristics to reduce the total number of bytes transferred over the wire and maximize performance.
Airbnb decided to improve the performance of their Products Details Page by implementing modern performance patterns. Although SSR has its pitfalls, Airbnb works around the “uncanny valley” by splitting each page up into multiple sections, and rendering them asynchronously based on their priority. Instant hydration of each section enables users to directly interact with the components, while loading and rendering the components with a lower priority when the browser is idle. Asynchronous section rendering ensures that Airbnb isn’t fetching resources that aren’t necessary to the user, providing a great user experience.
Thanks to AirBnB's Elliott Sprehn, Aditya and Callie for their input into this case study