How to Speed Up a Website

In our never-ending quest to create the best website experience possible, we realized that we needed to make page performance a priority. This would be no small feat given a website architecture that had been around for nearly a decade.

Why improve page speed?

While our website performance wasn’t terrible (our home page speed index was just over 3200), we understood faster websites result in reduced bounce rates, which factor into SEO rankings, which could bring in more prospective customers.

We were already implementing best practices, like using GZIP compression and minifying and concatenating our JavaScript and CSS files. But we knew there was still room for improvement.

Our goals for our top 20 pages were:

  1.  a score of 80%+ on desktop and 70%+ for mobile with Google PageSpeed Insights
  2.  a page speed index of 2500 or less with WebPagetest using the default settings (Chrome browser in a Dulles test location using a cable connection)

First Steps

Our website was redesigned in 2014 to be responsive. The modules and styles are contained in their own “Buildkit” repository. The project uses the Middleman framework to generate a style guide and documentation, Grunt for various tasks, Backbone for our JavaScript views, and SASS (with Susy and Compass) for our styles. Buildkit is integrated into our various applications as a Bower component.

Our designers and developers went through the modules and codebase with a fine tooth comb. This gave our developers an opportunity to better understand the codebase and designers a chance to measure the usability of the user interfaces.

Cleaning up our code

We took lots of tips from Lara Hogan’s book, Designing for Performance. For our first steps, we cleaned our HTML, JavaScript, and CSS. We trimmed out unneeded containers and used more semantic markup.

For our CSS, we used a mobile-first approach, eliminated duplicate properties, unused rules, and outdated vendor prefixes. We also took some tips from Addy Osmani’s post, Gulp And Grunt Tasks For Performance Optimization, switched to grunt-contrib-cssmin for minification instead of relying on the Middleman framework for compression. Our main CSS file went from 246KB to 233KB. We also added grunt-combine-media-queries and were able to squeeze out another 20KB to get the file down to 213KB.

We re-examined each integrated third party JavaScript library to determine if it was indeed needed. For example, Velocity.js was included to perform a smooth scroll to anchors on the same page. With jQuery, we were able to achieve the same effect. Because jQuery was already being used in the project, it made sense to get rid of Velocity.js.

We were already using sprites, but found newly introduced icons were not added to these. We audited all images on the website, added appropriate icons to our sprites, and drew others with CSS. This helped reduce the number of requests being made on most pages.

Over the past year, our homepage evolved from having 1950 DOM elements to 662—a 66% reduction. We had over 3000 unused CSS rules; now we have closer to 1300. Our main CSS file went from 40KB to 27KB (with GZIP compression).

Redesigning our UI modules

Our UX and creative teams designed new modules to replace large, complex ones. The old modules were image heavy and penalized our mobile users the most. One example was a bento carousel module. It was meant to inspire authors, but usability and heat map tests revealed that users never engaged with it. Removing it from home page decreased our page weight by 1.4MB and improved our average page load time by almost a second.

slow home page
A bento carousel with revealing content featured on our old home page in 2014.

It was replaced with a hero banner that displayed one single image (with a smaller version for mobile).

Blurb Home Page

Initial results

We were proud of what we achieved with our first steps. We cleaned up our codebase substantially and ensured we were using front-end best practices, but our page speed scores  didn’t budge and our average page load time only improved slightly.

Much of our content was hosted on a Drupal CMS. We did everything we could to improve our own codebase, but could do nothing about Drupal. We felt like we had our hands tied, and any further improvements would need to be made by consultants managing our CMS.

At around the same time, we had an internal hackathon. A group of software engineers built a static CMS driven by Jekyll. They proved that with a simple CMS, you can achieve a page speed score of 1000 and a PageSpeed Insights score close to 100%. Our product team was sold on the idea and earlier this year we started migrating to Jekyll.

[big_title2]Going static[/big_title2]

With a static website, you’re taking some content and templates and deploying a finished result. While performance is one benefit, there are others:

● Easy to set up

● Security

● Almost no down time

● Maintainability

The setup work took about four weeks. We included HTML minification from the get-go. After all, HTML is the critical path for all other resources being downloaded. We used Jekyll 3 Assets to minify and concatenate any CSS and JavaScript. We changed how we load our fonts by using Typekit’s “advanced” embed code to load files asynchronously. We worked closely with our DevOps team to setup CloudFront and GZIP compression, as well as NGINX to serve the new pages. We felt really good about where we were going.

Excited to see improved results, we migrated our top 20 pages from Drupal to Jekyll, and ran performance tests using PageSpeed Insights and WebPagetest. To our surprise, our scores were flat and worst in some cases.

We felt like we had our hands tied, and any further improvements would need to be made by consultants managing our CMS.

What went wrong?

We overlooked three key things:

1. Caching. We had no expire headers set on our assets.

2. Image compression. We were optimizing images in Adobe Photoshop, but we weren’t running files through a lossless image compressor.

3. Perceived performance. We were too heavily focused on page weight and page load times, and not enough on improving the user’s perception of speed.

Caching

Drupal was very good at caching, but we had completely overlooked this with our new static CMS. Our DevOps team helped us update our cache settings in NGINX to be more generous. Because we use Jekyll 3 Assets (which uses Sprockets) for our asset pipeline, we were able to set these to a one year expiration. However, the Buildkit CSS included via Bower does not run through the asset pipeline, so we had to be much more conservative here and only set this to a few hours. We also have no caching for our HTML files. We’re hoping to find a better solution for this.

Image compression

Our average page is made of up of 50% images, and on some pages images made up 80% of the page weight. We ran images for our top 20 pages through lossless image compressors, using whatever tool yielded the best results. ImageOptim is great and convenient, but we sometimes got better results with pngquant and TinyJPG.

Furthermore, we worked with our designers to integrate lossless compression into their workflow. They now use the TinyJPG Photoshop plugin when saving JPG and PNG files. And we automated this in our Buildkit project with grunt-image, which reduced the size of our Compass-generated sprites by up to 75%.

We were able to reduce page weight by up to 50% using better compression tools. Image compression has, by far, been the easiest way to optimize a page.

Perceived performance

Both PageSpeed Insights and WebPagetest measure how quickly content appears to the user. PageSpeed Insights more specifically, takes into account the speed at which above the fold content is rendered.

Perceived performance is how quickly a user thinks your site is. If we can make the web page legible and interactive even before it’s technically done loading, the user will be much happier. As Ilya Grigorik put it in his book High Performance Browser Networking, “Time is measured objectively but perceived subjectively, and experiences can be engineered to improve perceived performance.”

With that in mind we set out to improve the hero banner module that appears at the top of almost every page of the website. The image appearing in the banner is set with JavaScript, which meant that a gray background appeared until the JavaScript was executed.

Hero banner on our Business page at 1.38s.
Hero banner on our Business page at 1.38s.

By simply adding the styles for this module to the head section of the HTML page, the perceived performance improved as the banner loads much sooner, and we don’t have to wait for the related JavaScript to finish downloading.

Hero banner on our Business page at 1.38s.
Hero banner on our Business page after improvements at 602ms.

We also found loading scripts asynchronously or deferring their loading sometimes had negative impacts on perceived performance. Because Jekyll is a static CMS, we rely on JavaScript AJAX calls to pull parts of our navigation, like login and account links. If the user is logged in, these change to reflect that. We initially loaded the scripts asynchronously to adhere to best practices, but this caused the navigation to load too slowly, and thus pages appeared incomplete and sluggish. The scripts now load synchronously.

If we can make the web page legible and interactive even before it’s technically done loading, the user will be much happier.

There’s much more to perceived performance. These are some of our favorite resources:

● Tammy Everts, Understanding UX and Hacking Perceived Performance, New York Web Performance Meetup. Slides are on SlideShare

● Denys Mishunov, Why Performance Matters, Part 1: The Perception Of Time, Smashing Magazine

● Steve Souders, The Perception of Speed

Conclusion

We’ve reached our PageSpeed Insights goal of an 80% score for desktop, but fell short for mobile (many pages are at 68%). We met our goal for WebPagetest and most of our pages have a page speed index of 2500 or less, but a few are still at 3000+.

A few takeaways:

Performance is a team effort. It’s not just an engineering team’s responsibility. Our product team understands its importance and helps prioritize tasks and stories around performance. Our UX team designs intuitive user interfaces that take into account performance. Our design team makes everything optimized and beautiful. And our DevOps team helped us migrate to Jekyll and improve server configurations.

Reporting is hard. Perhaps the biggest challenge: keeping track of reports and historical data. This is obviously vital to measure the success of specific projects. Be sure to create an account on Webpagetest. The site will keep track of your test history, so it’s easy to go back to tests done a year ago

Deploy one improvement at a time. There were times when we bundled optimization work into a single deploy, so it was unclear what change was responsible for the page speed score improvement (or regression).

We’ll continue to optimize our website—there always seems to be room for improvement. Some projects on the horizon:

SVG Sprites. While we don’t anticipate this to affect our page speed scores, switching from PNG to SVG sprites will simplify our CSS as we currently have two versions of sprites (one for retina and another for non-retina displays). We’ll also be able to eliminate our Fontello font icons (one less request!).

Use appropriately sized images. We’re investigating using ImageMagick and other tools to automatically resize assets, so that we can serve appropriate sizes for each device.

Reduce third-party scripts. Third-party scripts are currently our biggest blocker to getting the scores we want. We are working with our marketing team and other stakeholders to come up with budgets, so we only load a max number of third-party scripts per page request.

Redesign more modules. Our designers are simplifying many of our modules. For example, our email signup form is currently a modal dialog that pops up on some pages. This form will be more minimal and moved into our footer, reducing the amount of client-side code needed to manage it.

Optimize our critical rendering path. At the moment, all devices load one big render-blocking CSS file. Mobile devices are penalized the most as they have to load all desktop-targeted styles. We want to add a Grunt task that divides our main stylesheet into three separate files based on media queries. Devices will still load all styles, but stylesheets targeted for larger devices won’t block rendering on smaller devices (see the “Critical Rendering Path” in Designing for Performance for more information).

Comment

This post doesn't have any comment. Be the first one!

hide comments

This is a unique website which will require a more modern browser to work!

Please upgrade today!