Carousell Insider
Published in

Carousell Insider

How we made Carousell’s mobile web experience 3x faster

A 6-months retrospective on building our Progressive Web App

🖼 The PWA at 🔎

Why a faster web experience?

🌩 The Lighthouse performance score that was a wake up call 🏠

How We Did It

Starting with a Real-world Performance Budget

Performance budgets keep everyone on the same [page]. They help to create a culture of shared enthusiasm for improving the lived user experience. Teams with budgets also find it easier to track and graph progress. This helps support executive sponsors who then have meaningful metrics to point to in justifying the investments being made.

Can You Afford It?: Real-world Web Performance Budgets.

Loading a web page is like a film strip that has three key moments. There’s: Is it happening? Is it useful? And, is it usable?

The Cost Of JavaScript In 2018

🔼 Our performance budget 🌟
⚠️ bundlesize blocking a PR that exceeded the budget 🚫

How we made it (seem) fast

  1. Adopting part of the PRPL pattern. We send the minimal amount of resources for each page request (using route-based code-splitting) and precache the rest of the app bundle using workbox. We also split out unnecessary components. For example, if a user is already logged in, the app would not load the login and sign up components. At present, we’re still deviating from the PRPL pattern in a couple of ways. First, the app has more than one app shell due to older pages that we haven’t had the time to redesign. Secondly, we haven’t explored generating separate builds for different browsers.
  2. Inlining critical CSS. We use webpack’s mini-css-extract-plugin to extract and inline each page’s critical CSS to improve Time to First Paint. This is to give the user the perception that something is happening.
  3. Lazy loading images not in the viewport. And progressively loading them when they are. We created a scroll observer component, based on react-lazyload, that listens to the scroll event and starts loading an image once it’s calculated to be inside the viewport.
  4. Compressing all images to reduce data transferred over the network. This came free with our CDN provider’s automatic image compression service. If you don’t use a CDN, or simply curious about performance for images, Addy Osmani created an amazing guide on how to automate image optimization.
  5. Using service workers to cache network requests. This reduces data usage for APIs that didn’t change often, and improved our app’s load times for subsequent visits. We found The Offline Cookbook helpful in deciding on which caching strategies to adopt. Since we had multiple app shells, Workbox’s default registerNavigationRoute didn’t work for us and we had to write a custom handler to match the navigation requests to the correct app shell.
⚙️ Using a network-first strategy with a 3s timeout for all our app shells 🐚

Results: How did we do?

🎉 Before and after comparison of the mobile web metrics 🎉
Before and after comparison of our Listing Page on a Nexus 5 with fast 3G. Update: WebPageTest’s “easy” report for this page. ⏭

Moving Fast, With Confidence

A consistent Carousell Design System

Going with the Flow


What helped us stay fast

  • Having and enforcing a perfomance budget
  • Reducing critical rendering path down to the minimum
  • Auditing often with Lighthouse

What helped us move fast

  • Having a standardised design system and its corresponding library of UI components
  • Having a fully typed codebase

Closing Thoughts



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store