March 9, 2018
Priyanka Aher
Time To Read
Estimated reading time: 9 minutes

Go Fast or Go Home: The Process of Optimizing for Client Performance

By Priyanka Aher in Client Side Web, Lessons Learned on March 9, 2018 | Responses (0)

Increasingly there are a lot of signs that indicate customers hate slow pages. According to data from Google and many other sources, customers leave a page or become frustrated if loading page takes longer than about three seconds.

In the beginning of 2016 the homepage for Expedia took up to 8 seconds to load for some customers in some cases. Our platform had evolved to serve a variety of complicated needs for our global and multi-branded business. Speed, while important, had not been made a top priority. Once our team completed the global replacement of the previous platform it was time to focus on performance and greatly increase the speed of our pages.

Our target was a two second page load. Getting to two seconds has been anything but easy or quick. It has been a long journey over a year and a half. This blog is condensed walkthrough of changes the team made to deliver a fast performing page, and some insights about how we keep it there.

Expedia Homepage Performance Optimizations

Our very first step (for performance as well as development reasons) was to migrate our home and launch pages out of our legacy shared application to a standalone AWS application. Serving the pages from a smaller, lighter application closer to the customers immediately improved our 50th percentile (TP50) page usable to 2.8 seconds, but we knew we could be a lot better. The performance improvements were focused on 2 broad areas; Google Page Speed Index improvements and lower page usable times.

Measuring: Page Speed Insights (PSI) and Page Usable metrics

PSI (Page Speed Insights) is a Google tool used to measure the performance of a webpage for mobile and desktop breakpoints. PageSpeed Insights checks to see if a page has applied common performance best practices and provides a score, which ranges from 0 to 100 points.

Our plan was to implement the PSI improvements as recommended by the Google Page Speed Insights. The following table chronicles changes in Homepage PSI scores over time, highlighting both changes that we made to improve performance, and some drops as new features from our team and other teams were rolled out onto the page.

Release Mobile PSI Change Desktop PSI Change Implementation Changes
  • Inlined Critical CSS for the above the fold content.
  • Removed render blocking ads script used to detect if ad-blocker is enabled. Instead, loaded it using AJAX.
R2 +14 +8
  • Increased browser caching time, for partner team resources on the homepage
R3 0 0
  • None
R4 -10 -2
  • PSI scores dropped due to uncompressed resources from changes in internal libraries from other teams
R5 -10 -13
  • PSI scores dropped due to unoptimized images in a new deals feature.
    Enabled gzip compression of resources for svgs in Storefront-web and Global Controls repo.
  • Moved subset of images from media server to s3, and set the browser cache time to 1 week.
R6 +4 +5
  • Image optimization using Thumbor for images in Top deals section and other UI libraries.
  • Enabled API compression on more Ajax calls.
R7 +16 +12
  • Applied above optimizations to the new features.
  • Target goal for PSI achieved.

You can see that there was not a straight-line progression; other teams also were adding features to the homepage and were not necessarily aware of our goals, which in some cases decreased our performance. This highlights the importance of communicating and enforcing time and performance budgets on large projects.

Page Speed Recommendations

Let’s walk through the main page speed recommendations we focused on and the implications of those changes.

Inlining Critical CSS

There are three ways to load and apply styles and css to a page:

  • Load synchronously
  • Inline
  • Load asynchronously


By default the css included on a page via a style tag:

<style src="style.css">

is loaded synchronously. Until the browser downloads the file, page rendering and javascript execution are blocked. This delays the browser from displaying anything on the page until all the css is downloaded which impacts rendering performance.


Inlined CSS will not block the page from rendering. Of course inlined CSS suffers from a variety of other problems. Redundancy in markup can result in larger page sizes, which increases the number of packets required to download the markup. The browser cannot cache the css of the page. Finally declaring styles inside markup is also notoriously difficult to maintain. For that reason it’s difficult to recommend inline CSS for much.


Loading css styles asynchronously will not block rendering or impact page usable. However, a user may see a flash of un-styled content (FOUC) in the time it takes for the CSS to load. To prevent a “FOUC”, inline the critical CSS of the page and load the rest of the CSS resources asynchronously in the background. ‘Critical css’ is a term that refers to the CSS for above-the-fold content that needs to be in place.

A node module called critical was used to generate the critical css, and a proxy tool called Charles was used to test the quality of the generated CSS. We used these tools to block the css resources from getting downloaded, which let us verify that the inlined css assets were good enough to prevent the flash of un-styled content.

Eliminate Render Blocking Scripts

Google Page Speed Insights recommends removing all render blocking javascript files from above the fold content, so that the DOM parsing does not stop.

There are two ways to achieve this, with either async or defer attributes on script tags. In both cases the script is downloaded in parallel while the DOM is being parsed, but the way they are executed is different.

Async only pauses DOM parsing to execute the script rather than during the fetch, while defer waits until the entire DOM has been parsed before it executes the script. Defer also executes scripts in the order in which they are present in the DOM, whereas async provides no execution guarantees. This article has good recommendations on when to use which element as appropriate.

Leverage Browser Caching

Downloading resources from the server is both time consuming and expensive. If a resource has been cached in the user’s browser, it saves the extra roundtrip time to download it. This is especially useful for large files which could require multiple roundtrips. PSI recommends that static resources be cached for a minimum of one week up to one year if they don’t change often.

Gzip compression for Scalable Vector Graphics (SVG)

Modern browsers support compression for all HTTP requests. Resources can be compressed by up to 90%, which significantly reduces the time to download and render the page. Smaller downloads also reduce the load on the customer’s mobile data plan. On the Homepage, SVG files were minified and compressed at build time using gulp-svgmin and gulp-gzip plugins.

Image optimization using Thumbor

An internal team had just launched an image optimization service powered by Thumbor. This service helps to serve images faster by allowing images to be compressed, cached, and intelligently resized, then served by Akamai or cached in the user’s browser, without the overhead of manually processing all the images before hand. Even though it requires additional processing time the first hit, it provides for a faster experience overall without exploding the maintenance requirements the image library.

Our other metric: Page Usable

Google Page Speed represents a series of optimizations that can be made, but how do we measure the real browser performance and more importantly its impact on the customer experience? We can start by observing and considering the real world rendering flow until the page is usable for the users.

It is out of scope for this article, but we have a logging system that tells us in aggregate how well customer’s browsers are performing. We can slice and dice this data by client type, which led to our starting hypothesis about mobile performance. Our hypothesis was that mobile users are even more sensitive to page load times, so we focused on getting them to two second performance first.

History of Page Usable scores and progress:

Version Time to Page Usable Changes
R0 2.8s
  • Image optimization and improving browser caching for some homepage modules.
  • Enable gzip wire compression for some required API calls
R1 2.7s
  • Inlined critical CSS
  • Moved more importance, critical javascript to load before some ancillary site features


R2 2.4s
  • Cleaned up and removed redundancy from advertising markup on the homepage, saving ~130k or about 15%.
R3 2.2s
  • Moved a significant amount of javascript responsible for rendering non-critical features from being synchronous to async, saving another 135k from page size.
R4 2.1s
  • Reduced the size of the page by removing legacy unused markup, removing 5k from the mobile site.
  • Removed 12k worth of code that wasn’t needed on mobile devices.

Reducing the HTML of the page reduces the amount of data that needs to be downloaded down the wire and speeds up parse times. We looked for areas to optimize and reduce the HTML size, such as eliminating markup not required to render the initial load (50% reduction) and templatizing elements on the page (10% reduction).

As a result of a previous feature implementation, the Homepage contained the markup for the home tab as well as all the launch pages like Flights, Hotels, Packages, etc.. After eliminating that extra markup, homepage only contained markup for the Home tab. The tradeoff was that if the user clicks on a different launch tab such as flights or hotels a new request will be made. The improved speed of home page rendering more than made up for the loss of that “instant switch” feature. That feature was previously a winner in an A/B test. This is just another example of needing to continually challenge assumptions, as the environment changes frequently.

For templatizing elements, instead of including the markup for all rooms on Hotels, Flights and Packages line of business, the page contained the markup only for the simplest version of the search wizard. As the user interacts with the wizard and requests more complicated searches, markup is added/removed using jQuery. These changed helped to shed 2-3 hundred milliseconds off of the PageUsable time.

Baseline Page Usable (mobile tp50) PSI (mobile)
Original Site on the legacy platform 7s 60
Improvements Page Usable (mobile tp50) PSI (mobile)
Migrate codebase to standalone AWS application 4s 58
Rewrite main search wizard with a custom, fast rendered version instead of the common version used in all contexts. 3s 58
Reduce use of server side library JSP tags in favor of plain HTML. 2.8s 58
Remove unseen markup previously used for instant tab switching 2.6s 58
Inline critical CSS, and lazy load lazy load the rest 2.5s 74
Templatize elements on page, reduce page size by 10% 2.4s 74

What did we learn?

As you can see from the sequence, many of these changes didn’t individually make a huge difference on their own. It is an extreme challenge to optimize a page that provides a lot of business value and has many business interests competing for features and placement, so you must take the performance gains piece by piece where you get them, rather than only searching out big wins. Setting a strong time budget goal, measuring it, and making sure that everyone understands the goal is just as important as the technical means you use to get you there.

Leave A Comment