Letting People in the Door. How and why to get 2s page loads.
The main benefit web has over native apps is it’s speed to deliver content. Within a few seconds of entering a URL into the browser, users can get information about our product and sign up for an account. But if you’re not conscious of the user’s overall experience you’ll lose users and revenue. At Capital One’s Level Money team our new web app (https://levelmoney.com/app) started with performance budgets to keep the user’s perspective a top priority.
TL;DR — Webpack config setting are at bit.ly/megatome
Why 2 seconds? Because users change their habits when longer than 2 seconds.
Having a 2 seconds page loads is the target. This is not because of an engineer’s preference but because page loads slower than 2 seconds has a negative impact on users. Case studies with Bing, Google, Amazon and others show that page loads longer than 2 seconds start impacting the user. As engineers and designers we are here to provide value to the people that come to us. One key point about our 2 second page load time is that it’s independent of the connection speed. Most people do not live in downtown New York or San Francisco. Most people in the US are either on 3G all of the time or their LTE connection drops to 3G speeds about 40% of the time. If you look at the standard distribution of user connection speeds, we want the target to hold true for 75 percent of our users. After researching current mobile vs desktop usage and US network speeds, 75% of users will have a 3G connection or better. That gives us a maximum network load of 400KB to everything to render the page (there are other factors as well but we’ll dig into that in a bit).
“Most people in the US are either on 3G all of the time or their LTE connection drops to 3G speeds about 40% of the time.”
Real user-focused metrics.
When we talk about page load we are do so from a user’s perspective. This means from the time the person hits “go” in their browser until useable content is rendered on the screen. A more precise term for this is Time to Interact (TTI). We’ll also be tracking the total abandonment rate; the percentage of user who never complete the first page load. This is tracked by recording a timestamp when the server receives the first request until we’re painted useful content on the screen.
Agreeing to agree. Users first and empowering designers.
When we first kicked off building our web app for @LevelMoney, we wanted to set proper expectations with the designers and stack holders. Just a single image can easily take 400KB. We don’t want to limit the designer’s freedom or sacrifice the UI just for faster page loads. Web fonts are largely hated by developers because of their increased page weight and lack of control over loading and caching. But I love fonts; they offer so much richness for users, so let’s do it right.
“I love fonts; they offer so much richness for users, so let’s do it right.”
For web fonts, we agreed to only use open source fonts. When you use a paid font from a font shop they enforce all kinds of rules around loading and caching that can degrade the user experience. By using open source fonts we are allowed to inline them, cache them forever, load subsets or the entire font. This affords us lot of options to deliver a better experience.
The next agreement was to allow fonts to fallback to system fonts if needed. Fonts have always had fallback rules. It’s a amazing feature but unless you get agreement up front from the designers they’ll want everything pixel perfect. Now we can decide that if and when to use system fonts. When the user’s connection is too slow we can ignore web fonts all together.
The last agreement on fonts was to minimize the variation of font weights and font families. Too many font weights and families will add delays to network loading and rendering. If a page has a font style that’s only used in one place it will block the entire page while it’s downloading. By discussing these trade-offs up front and by having continual collaboration we can focus on what’s best for the user.
Picking the right stack. Techno mumbo jumbo.
We started with ReactJS; a fabulous, life changing library that is unparalleled in terms of developer friendliness, page load speed and very performant at maintaining a good frame rate. React’s immediate mode (aka functional transforms) allows for operations to be add on top of or side-loaded without increasing the complexity or interleaving of processes. We can do things like drop into any state of the application and tab through every state. After 10 years of using everything from PHP to Rails to Backbone to UIKit, React is mind blowing.
“React is mind blowing.”
The third leg of our tri-force is ClojureScript. ClojureScript brings full functional paradigms to the browser. At it’s core it takes a single object and transforms it effectively and simply without introducing branching or complexities found in OOP languages. It’s asynchronous library core.async makes asynchronous work so simple and easy to understand. It’s been incredibly reliable with less than a handful of Clojurescript bugs during the build out of our web app.
Getting the biggest bang for the buck. Prioritizing optimizations.
What techniques will be the biggest impact on page load times for the least amount of work? We’ll start by removing all of the optimization in order to set a base line. All of our images for the base line have been properly sized to render on a Retina MacBook and are pre-optimized with TinyPNG. The baseline for our app is 4.2MB. Once all optimizations have been enabled only 301KB is needed to fully render the web page.
“The baseline for our app is 4.2MB. Once all optimizations have been enabled only 301KB is needed to fully render the web page.”
Most of these optimization steps are fairly common but there are 2 items to call out. For responsive images we’re selecting an alternate size of the same image that is no bigger than what needed to rendered on the user’s screen. The placeholder image technique that we use is a little more unique. Inside the HTML we have inlined a tiny image that is only 20 pixel wide. Then we stretch it’s width to 100% and put a heavy Gaussian blur on top. This will give the perception that the page is fully loaded sooner. Once the placeholder is rendered, we will download the responsive image and cross fade between to two. Using a placeholder image isn’t appropriate for all images but with big, full-bleed hero images it works beautifully.
Now with all of the optimizations, we’re 25% below our page weight target (even with 40KB of fonts). But in testing the page takes longer than 2 seconds to render. For the next phase we need to look at the overall system. What work is being done and what is the order of operations?
To simulate real-world usage over 3G, we’ll use Chrome Dev Tools. In the Network tab we’ll turn on screenshot capture feature (the video camera icon). The first paint here takes 2.3s but there’s no text. This is utterly useless for the user so we need to find the first time text is render, which is at 2.83s.
If we wanted we could remove the web fonts when on 3G connections. Web fonts add a lot of richness so removing them should be a last resort. The goal is to balance a great user experience with a speedy UI. If we removed our web fonts it only saves 0.4s. The impact is minimal and they’re better options to increase page speed.
The road ahead. Doing as little work as possible.
Pulling back and focusing on the user.
After all this, will 800ms make a difference for users? Will these simulated times hold up with real-world use cases? We will be tracking our abandonment rates, real-world load times and A/B testing features of the web app. We can also start gathering metrics to see if we went too far. At what point do faster page loads have no impact on user behavior?
With page loading behind us, I’m excited to start tackling the really fun items. The things that have bigger impact the complete user experience. A few items we’re looking at are the following.
- Better animations to provide context to the user and seamlessly integrate the experience.
- Offline mode (w/ service worker) to allow the app to work better in “Lie-Fi” mode.
- A/B testing our messaging to make sure we’re providing value telling people want we provide.
- Building and user testing more features to rapidly iterate our app and create a better product.
Webpack config details are at bit.ly/megatome.
Our numbers for US network speeds can be found at http://opensignal.com/reports/state-of-lte/usa-q1-2014 and http://www.clickz.com/clickz/column/2388915/why-mobile-web-still-matters-in-2015. Page loading case studies are here: http://www.guypo.com/17-statistics-to-sell-web-performance-optimization. More details from my public talks are hosted on github at http://puppybits.github.io/talks.