Building Tinder Online

Roderick Hsiao
5 min readFeb 9, 2018

--

Tinder — One of the most popular online dating service, is now available on the web platform worldwide.

Tinder Online

We start this journey not so long ago when the company already invested heavily on native app experience and advance machine learning technology.

We realize that not all users has the latest mobile device with big storage and ultra high speed network speed to run our native client. Web platform then serve a very good purpose — able to run mostly anywhere with a relative lite required resources.

Our web team has a relative small size, but we starts with a great mission — we want to deliver the performant and smooth web experience using cutting edge web technology.

Tinder Gold (Premium Feature)

Architecture

Tinder Online is built using a React/Redux stack.

To build a highly performant and scalable web app, we created our entire user interface using React, with a focus on building reusable components that are then composed within view containers. This flexible composability facilitates rapid iteration and a maintainable codebase.

We use a Redux store to persist our application state. Our state is constructed via ImmutableJS and Normalizr, which allows us to carry out efficient and performant state operations. Memorized selectors makes our store access highly performant.

Tinder Online — Swipe Anywhere

When we first rollout the experience to target markets, we are using a server-less solution. We deployed static assets to s3 and execute the full app logic client side. We then move to an isomorphic Node app to serve more complicated use cases.

We construct the initial application state (i.e. feature-flags, and internationalization) server-side using a simple NodeJS/Express server and render a highly cacheable app shell with dehydrated state client-side. The full application logic and data fetching flow is then initialized after rehydrating the application state.

Side-effects and asynchronous operations such as API requests are handled using Redux Sagas. We persist parts of our state such as user settings, location, and application settings with IndexDB in supported browsers, and fall back to localStorage when necessary. The persist store greatly improve the app start up performance and user experience.

The app rendering logic and routes settings are centralized and configured on the top level. This abstraction allows us to separate page-level logic from component-level logic and makes it easy to handle route-level code splitting and various page transition effects. We also develop a proxy react component to implement dynamic Javascript loading and resource preload for the next route.

The core swiping experience and animation is build on top of React Motion. Internationalization is handled by React Intl. We use React I13n to separate instrumentation logic from UI logic by creating pluggable listeners for different tracking systems.

Performance

Our goal is to provide a seamless experience similar to our native clients for most of our users regardless of network condition or device hardware restrictions. Therefore, performance is the top priority of us when building features.

We focus two main areas — Network performance and render performance.

Network performance

To support users with slower network, the web app is optimized to limit network load, document parsing time, and render time. In general, we want to load the critical assets early and fast and defer the optional resources.

We are able to greatly improve the initial load time by assigning individual resources priorities using link preload and prefetch along with code splitting. We ship the minimal resources to the client by implementing code splitting, pre-cache chunks via a service worker, and preload assets for next anticipated route efficiently. We are using Workbox to control high level service worker caching strategies for different resources.

The critical render path is optimized by inlining most of our common CSS. We are using Atomic CSS to create highly reusable and compressible stylesheets. With Atomic CSS, UI theming and display logic are controlled by React props, making our code easy to share and maintain. Our core CSS, which includes theming, spacing, and responsive styling, is about 10kB (gzip) for the whole site.

To prevent our bundle size increasing when adding new features, we set performance budgets for all of our resources. The size of our Javascript and CSS bundles are audited on each commit. Setting a good performance bundle enforces us to build highly shareable component. We also measure and track performance with tools such as Lighthouse and CSS stats prior to each release. Real time user monitoring metrics such as load time and paint time (PerformancePaintTiming) are collected client-side.

Our source code is compiled and polyfilled by Babel and generated by Webpack. By exercising bundle analysis, we were able to identify several opportunities for performance optimization strategies such as coding splitting, tree shaking, or selecting alternative libraries. We also use babel-preset-env to include only the subset of polyfills targeting our supported browsers. The total resources need for the web app is around 3mb, which is great for user who has limited device storage.

Render Performance

We optimize rendering and animation performance by prioritizing Javascript tasks using requestIdleCallback. Non critical tasks such as instrumentation will be scheduled to idle time. We also ensure that our HTML markup and CSS are highly optimized and lazy load offscreen assets via Interaction Observer for fast rendering and smooth performance, even on slower devices.

We use the Chrome dev tool and React developer tool heavily to identify performance bottleneck such as browser repaint, React re-render or high cost Javascript operations.

What’s next

Product-wise we are seeing very positive user engagement on the web platform.

In terms of technology, there are several area we would like to focus soon.

  • Experiment with different approaches for code splitting, such as deferring the registration of Redux reducers and saga handlers.
  • Utilize our service worker runtime caching more widely for a better offline experience.
  • Offload expensive tasks, such as parsing frequently-consumed API responses, to Web Workers.
  • Improve performance among modern browsers by experimenting with new browser primitives such as the network information API.
  • Experiment deploying ES module to supported browser
  • Rearchitect Redux store structure to enhance state management

We will also share more practice and learning to the community.

Special thanks to our friends Addy Osmani, Liam Spradlin, Cheney Tsai, and other folks at Google for providing great insights and suggestions for the Tinder progressive web app!

This blogpost is a collaborated works from all the Tinder Web team members. Amritha Arakali, Antony Chan, Brendan Todaro, Erik Hellenbrand, Jackie Wung, Jenny Peng, Keith McKnight, Salina Wu, and Sid Jain.

Related resources

--

--