Tinder recently swiped right on the web. Their new responsive Progressive Web AppTinder Online – is available to 100% of users on desktop and mobile, employing techniques for JavaScript performance optimization, Service Workers for network resilience and Push Notifications for chat engagement. Today we’ll walk through some of their performance learnings.

Journey to a Progressive Web App

Tinder Online started with the goal of getting adoption in new markets, striving to hit feature parity with V1 of Tinder’s experience on other platforms. The MVP for the PWA took 3 months to implement using React as their UI library and Redux for state management.

The result of their efforts is a PWA that delivers the core Tinder experience in 10% of the data-investment costs for someone in a data-costly or data-scarce market (2.8MB):

Early signs show good swiping, messaging and session length compared to the native app. With the PWA:

  • Users swipe more on web than their native apps
  • Users message more on web than their native apps
  • Users purchase on par with native apps
  • Users edit profiles more on web than on their native apps
  • Session times are longer on web than their native apps

 Tinder are looking forward to sharing more data about the business metrics from their PWA in the future.

Performance

The mobile devices Tinder Online’s users most commonly access their web experience with include:

  • Apple iPhone
  • Apple iPad
  • Samsung Galaxy S8
  • Samsung Galaxy S7
  • Motorola Moto G4

Using the Chrome User Experience report (CrUX), we’re able to learn that the majority of users accessing the site are on a 4G connection:

Note: Rick Viscomi recently covered CrUX on PerfPlanet and Inian Parameshwaran covered rUXt for better visualizing this data for the top 1M sites.

Testing the new experience out on WebPageTest using the Galaxy S7 on 4G we can see that they’re able to load and get interactive in 5.9 seconds:

There is of course to improve this further on median mobile hardware (like the Moto G4) as we can see from WebPageTest, however Tinder are hard at work on optimizing their experience and we look forward to hearing about their work on web performance in the near future.

Performance Optimization

Tinder were able to improve how quickly their pages could load and become interactive through a number of techniques. They implemented route-based code-splitting, introduced performance budgets and long-term asset caching.

Route-level code-splitting

Tinder initially had large, monolithic JavaScript bundles that delayed how quickly their experience could get interactive. These bundles contained code that wasn’t immediately needed to boot-up the core user experience, so it could be broken up using code-splitting. It’s generally useful to only ship code users need upfront and lazy-load the rest as needed.

To accomplish this, Tinder used React Router and React Loadable. As their application centralized all their route and rendering info a configuration base, they found it straight-forward to implement code splitting at the top level.  

In summary:

Use React Loadable to split out non-critical JavaScript into separate bundles. 

Use the webpack CommonsChunkPlugin to move common items up to a single bundle file.

They use React Loadable’s preload support to preload potential resources for the next page on control component.

Tinder Online also uses Service Workers to precache all their route level bundles and include routes that users are most likely to visit in the main bundle without code-splitting.

Impact

After introducing route-based code-splitting their main bundle sizes went down from 166KB to 101KB and DCL improved from 5.46s to 4.69s:

Long-term asset caching

Ensuring long-term caching of static resources output by webpack benefits from using [chunkhash] to add a cache-buster to each file. Tinder were using a number of open-source (vendor) libraries as part of their dependency tree. Changes to these libraries would originally cause the [chunkhash] to change and invalidate their cache. 

To address this, Tinder began defining a whitelist of external dependencies and splitting out their webpack manifest from the main chunk to improve caching. The bundle size is now about 160KB for both chunks.

Preloading

As a refresher, link rel=preload is a declarative instruction to the browser to load critical, late-discovered resources earlier on. In single-page applications, these resources can sometimes be JavaScript bundles.

Tinder implemented support for link rel=preload to preload their critical JavaScript/webpack bundles that were important for the core experience. This reduced load time by 1s and first paint from 1000ms to about 500ms. 

Performance budgets

Tinder adopted performance budgets for helping them hit their performance goals on mobile. As Alex Russell noted in “Can you afford it?: real-world performance budgets“, you have a limited headroom to deliver an experience when considering slow 3G connections being used on median mobile hardware. 

To get and stay interactive quickly, Tinder enforced a budget of ~155KB for their main and vendor chunks, asynchronous (lazily loaded) chunks are ~55KB and other chunks are ~35KB. CSS has a limit of 20KB. This was crucial to ensuring they were able to avoid regressing on performance.

Webpack Bundle Analysis

Webpack Bundle Analyzer allows you to discover what the dependency graph for your JavaScript bundles looks like so you can discover whether there’s low-hanging fruit to optimize. 

Tinder used Webpack Bundle Analyzer to discover areas for improvement:

  • Polyfills: Tinder are targeting modern browsers with their experience but also support IE11 and Android 4.4 and above. For polyfills, they are including core-js and use babel-preset-env to remove unused polyfills.
  • Slimmer use of libraries: Tinder replaced localForage with direct use of IndexedDB
  • Better splitting: Split out components from the main bundles which were not required for first paint/interactive
  • Created asynchronous common chunks to abstract chunk use more than three times from children
  • Tinder also removed critical CSS from their core bundles (as they had shifted to server-side rendering and delivered this CSS anyway)

This also included taking advantage of Webpack’s Lodash Module Replacement Plugin. The plugin creates smaller Lodash builds by replacing feature sets of modules with noop, identity or simpler alternatives:

Webpack Bundle Analyzer can be integrated into your Webpack config and Tinder’s setup looks like this:

plugins: [
  new BundleAnalyzerPlugin({
    analyzerMode: 'server',
    analyzerPort: 8888,
    reportFilename: 'report.html',
    openAnalyzer: true,
    generateStatsFile: false,
    statsFilename: 'stats.json',
    statsOptions: null
  })
] 

The majority of the JavaScript left is the main chunk which is trickier to split out without architecture changes to Redux Reducer and Saga Register.

CSS Strategy

Tinder are using Atomic CSS to create highly reusable CSS styles. All of these atomic CSS styles are inlined in the initial paint and some of the rest of the CSS is loaded in the stylesheet (including animation or base/reset styles). Critical styles have a maximum size of 20KB gzipped, with recent builds coming in at a lean < 11KB.

Tinder use CSS stats and Google Analytics for each release to keep track of what has changed. Before Atomic CSS was being used, average page load times were ~6.75s. After they were ~5.75s.

Runtime performance

Deferring non-critical work with requestIdleCallback()

To improve runtime performance, Tinder opted to use requestIdleCallback() to defer non-critical actions into idle time. This included work like instrumentation beacons. They also simplified some HTML composite layers to reduce paint count while swiping.

Before using requestIdleCallback() for their instrumentation beacons:

and after..

Dependency upgrades

In older versions of webpack, when bundling each module in your bundle would be wrapped in individual function closures. These wrapper functions made it slower for your JavaScript to execute in the browser. Webpack 3 introduced the ability to concatenate the scope of all your modules into one closure and allow for your code to have a faster execution time in the browser. It accomplished this using:

new webpack.optimize.ModuleConcatenationPlugin()

This is called scope hoisting. This improved initial JavaScript parsing time for Tinder’s vendor bundle by 8%. Updating to the latest version of React (React 16) also reduced the size of their vendor chunk by ~6.7%.

Workbox for network resilience and offline asset caching

Tinder also use the Workbox Webpack plugin for caching both their Application Shell and their core static assets like their main, vendor, manifest bundles and CSS. This enables network resilience for repeat visits and ensures that the application starts-up more quickly when a user returns for subsequent visits.

config.prod.js.txt

Opportunities

Digging into the Tinder bundles using source-map-explorer (another bundle analysis tool), there are additional opportunities for reducing payload size. Before logging in, components like Facebook Photos, notifications, messaging and captchas are fetched. Moving these away from the critical path could save up to 20% off the main bundle:

Another dependency in the critical path is a 200KB Facebook SDK script. Dropping this script (which could be lazily loaded when needed) could shave 1 second off initial loading time.

Conclusions

Tinder are still iterating on their Progressive Web App but have already started to see positive results from the fruits of their labor. I’m excited to hear more about their progress in the near future!

With thanks and congrats to Roderick Hsiao, Jordan Banafsheha, and Erik Hellenbrand for launching Tinder Online and their input to this article. Thanks to Cheney Tsai for his review too.

ABOUT THE AUTHOR

Addy Osmani photo

Addy Osmani (@addyosmani) is an Engineering Manager working with the Chrome and Web Developer Relations teams at Google. He's written open-source books like 'Learning JavaScript Design Patterns' and 'Essential Image Optimization' and created open-source projects like Yeoman, HNPWA and Critical. You can find more of his work on web performance over on his Medium channel.

One Response to “A Tinder Progressive Web App Performance Case Study”

  1. Chris Love

    Its good to see the effort to improve the performance from such a high profile property. As I look at the JavaScript sizes I can’t help but think they could be even smaller. It would be cool to have a tool that visualized just how much code you use and don’t use.
    The other thing I have to ponder is ‘do they need a SPA’ after migrating to a PWA? More to come on that thought soon….. 🙂

Leave a Reply

You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
And here's a tool to convert HTML entities