Xfive.co – Designed and Built for Performance

In these times of website obesity, we’ve made our website four times leaner than the average. How did we do it?

The average web page size has increased to 2.3MB in April 2016. According to WebPagetest our home page size is 538kB consisting of 13 requests on the initial load. I’d like to share some insights for designing and developing a performance-oriented website.

Thinking performance: The rebranding phase

Our company underwent a major rebrand in January 2016. We started to plan to rebrand early in 2015. One of the main tasks was to redesign the old website so that it was fast, secure and easy to maintain.

We didn’t set up a performance budget but rather started with this thought in mind:

The new version of our website should perform better than the previous one.

First, we tested our old XHTMLized website. It had a load of 1.7 MB – slightly below the average at that time, and 81 requests.

XHTMLized’s home page with a robot mascot in April 2015
XHTMLized’s home page with a robot mascot in April 2015

Once you become interested in performance, you see your website through different eyes:

  • Do we need a chat script on homepage or blog posts?
  • Do our users care about how many likes we have on Facebook?
  • And why on earth we don’t have gzip compression enabled?

With just those fixes we were able to reduce our old website’s home page size to 732 kB and 37 requests. That set up a good starting point to compare our new website to.

Designing for performance: The redesign phase

For the design phase, we teamed up with a talented designer Adrian Hayes from Pan Fried Pixels. We ended up with the following design:

New Xfive home page showing the human face of our company
New Xfive home page showing the human face of our company

One of the goals of our new website was to show the human face of our company. Human face equals photos. Photos equal kilobytes, and usually a lot. Add two typefaces, sans serif for titles and serif for texts, and your kilobyte diet plan starts to fall apart.

Once you sense the delicious smell of pixels roasting, you can easily forget your New Year’s resolution of staying lean.

Designing for performance at this stage requires discipline as performance is often something distant and abstract. That’s why design decisions shouldn’t be set in stone, and you should be able to revise them during development.

Let me share two examples with you:

Using Disqus vs. WordPress for user comments

We planned to use the Disqus system for the comments on our blog. Later we found out that Disqus was adding 2.5MB of resources to the site!

That was simply too much even though Disqus loads asynchronously. Since our site is built on top of WordPress, we decided to stay with native WordPress comments. We could make this argument because the advanced commenting functionality wasn’t an essential feature of our site.

Social Media Buttons

We considered using a third party plugin for page sharing features. We tested the available solutions and saw how much weight they would add to the site. In the end, they were all too heavy, so we built our fast social media buttons with counters.

Building for performance: The Development phase

Here is a quick outline of the technologies we use at Xfive.co

  • WordPress with the Timber library
  • npm as a build tool for the Xfive theme
  • inline SVG icons, responsive images, and progressive JPGs
  • ITCSS as a scalable and maintainable CSS architecture
  • inlined critical CSS and optimized Google Fonts loading
  • Browserify to bundle JavaScript modules (no jQuery and all JS deferred)
  • PHP 7 and Varnish as a HTTP proxy cache
  • Cloudflare CDN for static assets like images, styles and JavaScript

Let’s take a closer look on some of these.

WordPress

WordPress was a natural choice. We like it; we do a lot of WordPress development for clients, and our old website ran on WordPress too.

WordPress theme development isn’t the cleanest job in the development world. That’s why we have chosen an awesome Timber library which uses the Twig template engine and allows better separation of concerns. Combine Timber with a powerful Advanced Custom Fields PRO plugin and your PHP files will often be just a few lines long:

// template-about.php for Xfive About page
$context = Timber::get_context();
$post = new TimberPost();
$context['post'] = $post;

// Reasons
$context['reasons'] = Timber::get_posts( array( 'post_type' => 'reason' ) );

// Testimonials
$context['testimonials'] = Timber::get_posts( array( 'post_type' => 'testimonial', 'numberposts' => 3 ) );

Timber::render( array( 'template-about.twig' ), $context );

To further increase the maintainability and transferability of our code base we have moved some non-theme related functionality to a custom functionality plugin (e.g., custom post types and taxonomies definitions).

While Timber and ACF can increase your productivity and make your code cleaner and more maintainable, they are also responsible for a significant portion of the page generation time. You should avoid regenerating your pages on each hit. Timber has built-in cache support, but we settled with the more transparent Varnish HTTP cache.

Managed WordPress hosting plans like Pantheon often use Varnish or other types of server caching, so you don’t have to be afraid of any performance loss.

npm build tools

Once we have the basic setup for WordPress done, we start theme development. Instead of developing the front-end separately and merging it later, we develop it right away with the theme functionality. This saves us around 20% of development time, but it also has an important psychological effect, as we know that this is the final stage and that there won’t be any development later. It’s easier to know that we are building a final product.

We setup the following npm tasks to help us with the theme development:

"scripts": {
  "sass": "node-sass --include-path assets/bower_components --source-map true assets/scss/main.scss dist/css/main.css",
  "build:css": "node-sass --include-path assets/bower_components --source-map true assets/scss/main.scss dist/css/main.min.css && postcss --use autoprefixer -b \"last 2 versions\" --use cssnano -o dist/css/main.min.css dist/css/main.min.css",
  "build:critical": "node-sass --include-path assets/bower_components --include-path assets/scss assets/scss/critical --output dist/css/critical && postcss --use autoprefixer --use cssnano -d dist/css/critical dist/css/critical/*.css",
  "lint": "jshint assets/js",
  "build:js": "browserify assets/js/main.js -d -p [minifyify --map bundle.map.json --output dist/js/bundle.map.json] > dist/js/bundle.js",
  "build": "npm run build:css -s && npm run build:critical -s && npm run build:js -s",
  "imagemin": "imagemin assets/img dist/img",
  "watch:css": "chokidar assets/scss -c \"npm run sass\" -d 10",
  "watch:js": "chokidar assets/js -c \"npm run build:js\" -d 10",
  "watch:images": "chokidar assets/img -c \"npm run imagemin\" -d 10",
  "browsersync": "browser-sync start --config bs-config.js",
  "dev": "parallelshell \"npm run watch:css\" \"npm run watch:js\" \"npm run watch:images\" \"npm run browsersync\""
},

While using npm as a build tool looks good in theory (just one file and a bunch of commands) next time we would use gulp. npm commands can easily become cluttered and the Browsersync reload and style injection wasn’t very smooth. (Last time I checked this it worked better with parallelshell replaced with npm-run-all.)

For WordPress development with Browsersync, you need to proxy your WP site. This is a benefit because it allows you to run two versions side by side:

  • At localhost:3000 you can run a dev mode with quickly built, un-minified CSS and you don’t have to include critical CSS.
  • At virtual host (in our case local-xfive.co) you can include prefixed and minified CSS and also critical CSS.

To achieve this, modify the request headers in browsersync before they hit WordPress. Then in WordPress:

// Check if we are running through Browsersync proxy to enable dev mode
if ( isset( $_SERVER['HTTP_PROXY']) && $_SERVER['HTTP_PROXY'] == "browser-sync" ) {
  $context['main_css'] = "main.css";
} else {
  $context['main_css'] = "main.min.css";
}

Once in awhile build your CSS and check at the virtual host if everything works as expected when CSS is minified and prefixed.

I would also recommend incorporating unique file names for assets like CSS and JS. This is important so you can set far future expiration dates to cache these assets efficiently.

Images

We use responsive images, which is again, an area where Timber shines in its compact notation:

<img src="{{post.thumbnail.src}}" alt="" class="c-hero__featured-image"
  srcset="{{post.thumbnail.src('small')}} 320w,
          {{post.thumbnail.src('medium')}} 768w,
          {{post.thumbnail.src('large')}} 1024w,
          {{post.thumbnail.src}} 1600w"
  sizes="(min-width: 64em) calc(100vw - 68px), 100vw">

Timber also has a handy resize filter which you can use directly in templates for your custom sized images.

We use progressive JPEGs and lazy load images below the fold using the lazysizes library. To avoid page jumps and repaints as a user scrolls down, we implemented Intrinsic Placeholders for these images.

For the icons, we use inline SVG icons. They are scalable, reduce the number of HTTP requests and are easy to maintain. Check out the following article for instructions how to create them.

We have also created guidelines for image optimization, so the content editors know how to find a right balance between the image quality and its size.

JavaScript

We separated our JavaScript functionality to small independent modules and used Browserify to bundle all dependencies together into a single minified file.

We knew we didn’t want to use jQuery. We don’t have anything against it, but every kilobyte counts. Also, it’s 2016 and if you don’t support old, insecure IE browsers, there is no reason why you cannot just use good old pure JavaScript or be on the bleeding edge and use ES6.

When you use jQuery and its countless plugins, it’s easy to forget about their impact on the overall page size. When you remove jQuery from your workflow, it’s usually because of your performance goals. Then you more carefully consider the effect of each particular plugin on your site size and performance.

Want a way to require WordPress not to include JQuery by default?

Here is a small snippet from our functionality plugin to deregister jQuery and defer all JavaScript:

namespace Xfive;

class OptimizeJavaScript {

  /**
   * Run actions and filters
   */
  public function run() {
    add_filter('script_loader_tag', array($this, 'defer_scripts'));
    add_filter('wp_print_scripts', array($this, 'remove_unnecessary_scripts'), 100);
  }

  /**
   * Defer scripts
   * @param  string $tag Script tag
   * @return string      Script tag
   */
  public function defer_scripts( $tag ) {
    if ( is_admin() ) {
      return $tag;
    }
    return str_replace( ' src', ' defer="defer" src', $tag );
  }

  /**
   * Remove scripts
   */
  public function remove_unnecessary_scripts() {
    wp_deregister_script( 'jquery' );
    wp_deregister_script( 'wp-embed' );
  }
}

Eliminating render blocking resources

To avoid render blocking resources like JavaScript or CSS, we have deferred execution of all JavaScript (see above how to do that in WordPress) and implemented critical CSS.

Critical CSS means that the CSS for the above the fold content is inlined in HTML. It can be a bit tricky to do, but it’s still considered a state-of-the performance method of loading CSS, at least until the more convenient and flexible methods are widespread.

We don’t use any automatic method for generating critical CSS. We have a main stylesheet with all styles, and we use loadCSS function to load it asynchronously. Then we have a common critical stylesheet (Normalize.css and common styles, styles for the common above the fold components) and individual critical stylesheets for most site templates. In the templates, we include common critical CSS and template specific critical CSS if needed.

{# template-about.twig #}
{% extends "base.twig" %}
{% set critical_css = 'about.css' %}

This way on the home page we were able to save around 12kB of inline CSS coming from other pages. This is important as inline styles add a lot to the HTML weight especially when using inline SVG yet.

We also use localStorage and WOFF2 fonts to optimize Google Fonts loading.

Performance Results

Here are some results for the Xfive home page from various performance tests:

  • Google PageSpeed Insights96-99/100 for the both mobile and desktop. Results vary depending how fast our server responds at the moment.
  • Webpagetest
    • Amsterdam – B grade for server response time (time to the first byte), D grade for caching (we cannot cache Google and Pingdom resources), A grade for other criteria.
    • Dulles, VA – F grade for the first time byte, D grade for caching, A grade for other criteria
  • Pingdom from Dallas, US – 86/100 – points lost because the requests for fonts URL don’t fit in a single packet. Pingdom also reported a long wait time (time to first byte) – 1.4s
  • GTMetrix from Vancouver, CanadaPageSpeed 99/100, YSlow – 96/100, slow load time reported – 1.82s waiting time

As you can see, we are achieving pretty good results in the various performance test. The only problem is a long time to the first byte (TTFB) reported by these tools. In all tests, the page was generated from Varnish cache which can be verified in the response headers. So this shouldn’t be an issue with WordPress itself.

So what could the problem be? We run our website on our server in Germany, and most of these test are run from the US servers. Indeed, if we check the sites from European locations, the time to the first byte decreases significantly.

Moreover, we use Cloudflare, so the request from a US visitor goes to the nearest Cloudflare data center in the US, then Cloudflare sends it to our server and passes the response back to the visitor.

Could it be that these requests simply take more time because they need to travel longer physical distance from the US to Europe? Possibly, although differences shouldn’t be that big. Cloudflare and TTFB (Time to First Byte) have never been best friends, and this topic raises some controversial discussion. TTFB doesn’t automatically mean slow loading times, but we will continue to investigate this.

We use Pingdom for uptime checks, transaction tests and Real User Monitoring (RUM). RUM shows that median loading time of our home page from US is around 2 – 2.5 seconds. That’s not bad, but we feel it could be better if we can improve TTFB.

Conclusion

Are the results what we hoped for? Not exactly, but they are pretty close. Web performance is a complicated matter and even if you do many things right, there still might be some things you need to work on or that are out of your control.

Was the effort worth it? Definitely. Not only we have learned a lot of things while building Xfive.co, but when we started orienting on performance our mindset changed completely.

Lean websites not only mean performant websites, but they also mean less annoying websites

Some our decisions like not using popups for collecting blog subscribers were partially influenced by performance concerns and partially by our desire not to annoy our visitors.

If you care about your users, you care about every byte you send their way.

Related posts

We're hiring 👋