Over the past few months, we of the Gawker Tech Team have put a ton of work into improving site performance and stability. If you don't recall seeing much evidence at all of the latter, you're right: we're only just now at the point of our time investment starting to pay off. More about that in a subsequent post! This post is about the performance side.
In the Google Analytics chart above, you can see that there have been steady improvements in load time from January up to now. We achieved these primarily by:
- Replacing sequential requests with ones that can be made in parallel, and cleaning up unneeded requests.
- Reducing the size of the styles and scripts that are needed to render a page.
- Freezing animated GIFs (pronounced "Gifs", with a hard "G"). Some sites, like Sploid, love to put GIFs all over the place, and these things add up (and even crash mobile browsers). Animated GIFs on mobile are now click-to-play, and on desktop they only play once scrolled to.
- Following general performance best practices, such as ensuring that our assets are cached for as long as possible.
These improvements offer much more than a theoretical advantage or an improvement in PageSpeed score. Here's a comparison of what a user's second view of the Valleywag homepage looked like on January 28 vs. on May 14th:
(Content starts displaying a lot faster and you can start reading about depressing tech stuff sooner.)
Here are details on some of the specific optimizations that got us here:
1. Deanimating GIFs*
Some people go through life joylessly, seeing the world through a prism of despair. The rest of us enjoy GIFs. And because we enjoy GIFs, we should understand their technical limitations and work around these as much as possible. GIF is an ancient file format that was really not made for motion. Since the time the animated GIF was introduced, a number of dedicated video formats have been released, which can offer order of magnitude improvements in efficiency. We're working on converting to a proper video format, but for the time being, we:
- (On desktop) show a thumbnail for GIFs, automatically loading the real GIF only when it is currently visible on the page
- (On mobile) show a thumbnail for GIFs with a Play button. The GIF plays when the user taps the Play button.
This is a GIF of a GIF that shows up with a Play button on mobile. (On mobile, this GIF will show up with a Play button.)
Besides a huge improvement in load time on pages with GIFs, this is much nicer to users on mobile data plans. Imagine that you're an American traveling to France, and that you have a Verizon phone which you neglected to switch from the default pay-as-you-go roaming option. In between wine tastings on the banks of the Garonne, you decide to see what's up on Sploid, and you navigate to this page:
In the past, because of all those GIFs, you would have incurred nearly $300 in data costs just loading that page! Now you're only charged for the GIFs you actually use. This is the way things should be.
1. "Main" - includes common dependencies for all pages
2. Context-specific layer (for example, "FrontPage" or "Post")
In the past, we needed to perform all of these steps in order to load a page's JS:
- Fetch "Main" JS layer
- Execute the "Main" layer
- Wait for a config file to load
- "Main" JS finds which context-specific layer we need and retrieves and executes it
After optimization, the steps look like:
- Fetch "Main" JS layer (now including config file) and context-specific bundle in parallel
- Execute "Main" JS layer, which in turn executes context-specific layer
We could have squashed the layers together, which would simplify the logic a good amount. But this would yield one giant, frequently-changing blob of JS. Visitors would be less likely to have the latest version of it cached as they browse the site, and so would be fetching a very large file more frequently.
There were a few other places we made operations non-blocking:
Reducing Page Weight
We built a few tools to aid us in reducing the size of scripts and other dependencies. One, soysauce, analyzes Closure Template dependencies, and among other things lets us know if we're pulling in unneeded templates.
Using these tools as well as some common sense we were able to dramatically reduce the amount of code needed on every page, and are continuing to monitor the codebase for things we can clean up.
About the last, biggest drop in early April, which allowed me to use the word "halved" and makes the chart look as impressive as it does: this is partly a lesson in how you shouldn't trust charts (and people bragging about their results). Around that time, we decided to show more images on our mobile view, which was previously a little sparse. To prevent phones from having to immediately load a large number of images on their limited connections, we started displaying "below-the-fold" images only after the user scrolled. This makes the numbers look pretty fantastic, but the perceived performance improvement on the user's side is disproportionately small to our earlier changes, because most browsers are pretty good at downloading images without blocking other site functionality from loading in. (Some good related discussion here).
So, in summary: we halved our load time by following recommended performance practices, reining in our GIFs, and also cheating a little. This is just the start, we're continuing to focus on making Kinja a speedy experience.
For more on frontend performance optimization, check out Google's performance best practices guide and High Performance Websites by Steve Souders. If you would prefer to read stuff written by a dog, check dog.gawker.com.
* "GIF" is pronounced "Gif", with a hard "G".