A one year PWA retrospective
By: Pinterest Engineering
Tags: JavaScript, React, Progressive Web App, Mobile, Web

Zack Argyle | Engineering manager, Core Experience

The idea of building a “Progressive Web App” (PWA) is not new, but its definition has changed with the emergence of key technologies like service workers. Now it’s finally possible to build great experiences in a mobile browser. Being an early adopter can be scary, so we’d like to share a brief overview of our experience building one of the world’s largest progressive web apps.

Three years ago we looked at the state of our website on mobile browsers and groaned at the obvious deficiencies. Metrics pointed to an 80 percent higher engagement rate in our native apps, so the decision was made to go all-in on our apps for iOS and Android. Despite increasing our app downloads substantially, there were some obvious downsides.

Emily. Owen. We would like to take this moment to offer an apology. You were right. It was terrible.

One year ago (July, 2017) we brought a team together to rewrite our mobile website from scratch as a PWA. This was the culmination of several years of conversation, months of metrics investigation and one large hypothesis: mobile web can be as good as a native app. The results are quite…pinteresting.

Why did we do it?

There were two main reasons why we reinvested so heavily in our mobile web. The first was our users. Our mobile web experience for people in low-bandwidth environments and limited data plans was not good. With more than half of all Pinners based outside the U.S., building a first-class mobile website was an opportunity to make Pinterest more accessible globally, and ultimately improve the experience for everyone.

The second reason was data-driven. Because the experience wasn’t great, a very small percentage of the unauthenticated users that landed on our mobile web site either installed the app, signed up or logged in. It was not a good funnel. Even if we weigh the native app users more heavily for higher engagement than mobile web users, it’s not the type of conversion rate anyone strives for. We thought we could do better.

How did we do it?

In July 2017, we formed a team that combined engineers from our web platform and growth teams. Internally, we called it “Project Duplo,” inspired by simplicity and accessibility. At the time, the mobile website accounted for less than 10 percent of our total signups (for context, the desktop website drove 5x that).

Timeline

July 2017: Begin “Project Duplo”

Aug. 2017: Launch new mobile site for percentage of logged-in users

Sept. 2017: Ship new mobile site for logged-in users

Jan. 2018: Launch new mobile site for percentage of logged-out users

Feb. 2018: Ship new mobile site for logged-out users

Part of the reason we were able to create and ship a full-featured rewrite in three months was thanks to our open-source UI library, Gestalt. At Pinterest, we use React 16 for all web development. Gestalt’s suite of components are built to encompass our design language, which makes it very easy to create consistently beautiful pages without worrying about CSS. We created a suite of mobile web-specific layout components for creating consistently spaced pages throughout the site. FullWidth breaks out of the default boundaries of PageContainer, which breaks out of the boundaries of a FixedHeader. This kind of compositional layout led to fast, bug-free UI development.

In addition to Gestalt, we also used react, react-router v4, redux, redux-thunk, react-redux, normalizr, reselect, flow and prettier.

How we made it fast!

Performance was baked into the goals and process because of how tightly correlated it is to engagement, and how sensitive it is on a mobile connection. In fact, our home page Javascript payload went from ~490kb to ~190kb. This was achieved through code-splitting at the route level by default, encouraging use of a <Loader> component for component-level code-splitting. An easy-to-use route preloading system was built into our client-side router, which creates a fast experience for initial page load as well as client-side route changes. For more details on how we made it fast, check out a case study we did with Addy Osmani.

After one year, there are ~600 Javascript files in our mobile web codebase, and all it takes is one ill-chosen import to bloat your bundle. It’s really hard to maintain performance! We share code extensively across subsites for *.pinterest.com, and so we have certain measures set up to ensure that mobile web’s dependencies stay clean. First is a set of graphs reporting build sizes with alerts for when bundles exceed permitted growth rates. Second is a custom eslint rule that disallows importing from files and directories we know are dependency-heavy and will bloat the bundle. For example, mobile web cannot import from the desktop web codebase, but we have a directory of “safe” packages that can be shared across both. There’s still work to do, but we’re proud of where we are.

While the case study deals mostly with page load, we also cared deeply about a fast, native-like experience while browsing. The biggest driver of client-side performance was our normalized redux store which allows for near-instant route changes. By having a single source of truth for models, like a Pin or user, it makes it trivial to show the information you have while waiting for more to load. For example, if you browse a feed of Pins, we have information about each Pin. When you tap on one, it takes you to a detailed view. Because the Pin data is normalized, we can easily show the limited details we have from the feed view until the full details finish being fetched from the server. When you click on a user avatar, we show that user’s profile with the information we have while we fetch the full user details. If you’re interested in the structure of our state or the flow of our actions, the redux devtools extension is enabled in production for our mobile web site.

At the heart of the new site was our attempt at building a truly progressive web app (PWA). We support an app shell, add to homescreen, push notifications and asset caching. The service worker caches a server-rendered, user-specific app shell that’s used for subsequent page loads and creates near instant page refreshes. We’re excited that Apple is building support for service workers in Safari so that all users can have the best “native-like” experience.

And what kind of “native” experience would it be without a “night mode”? Head over to your user settings and try it out!

The verdict

Now for the part you’ve all been waiting for: the numbers. Weekly active users on mobile web have increased 103 percent year-over-year overall, with a 156 percent increase in Brazil and 312 percent increase in India. On the engagement side, session length increased by 296 percent, the number of Pins seen increased by 401 percent and people were 295 percent more likely to save a Pin to a board. Those are amazing in and of themselves, but the growth front is where things really shined. Logins increased by 370 percent and new signups increased by 843 percent year-over-year. Since we shipped the new experience, mobile web has become the top platform for new signups. And for fun, in less than 6 months since fully shipping, we already have 800 thousand weekly users using our PWA like a native app (from their homescreen).

Looking back over one full year since we started rebuilding our mobile web, we’re so proud of the experience we’ve created for our users. Not only is it significantly faster, it’s also our first platform to support right-to-left languages and “night mode.” Investing in a full-featured PWA has exceeded our expectations. And we’re just getting started.

* Huge shout out to the engineers and product managers who worked on the rewrite: Becky Stoneman, Ben Finkle, Yen-Wei Liu, Langtian Lang, Victoria Kwong, Luna Ruan, Iris Wang and Imad Elyafi.