JP's blog

Tech talk with a french twist.

Squeezing Octopress for Faster Load Times

This blog runs an out of the box Octopress setup. I love Octopress. It’s a great framework. It’s trivial to setup, gets out of your way and it just works.

Being passionate about web performance optimization, I decided to “eat my own dog food” and look at how I could improve the load time of my blog.

Baseline

I’ve measured the homepage of the blog with my usual weapon of choice, WebPagetest. Using Firefox and Chrome roughly yielded the same results:

  • DOM ready fires at about 1.2 seconds.
  • The page is fully loaded between 2.8 and 3 seconds.
  • There are 21 HTTP requests for a total of 244KB.

Optimizing Javascript

The first obvious improvment is with javascript. The default Octopress installation will load 4 javascript files as shown in the screenshot below:

This is bad because browsers usually have a limit in the number of concurrent downloads they can perform at the same time. While this limit has been increased in the recent years, it is still a good practice to concatenate all the Javascript resources into one file. I created a file under source/javascripts/all.js in which I combined all 4 files. While I was at it, I minified all.js to make it smaller and moved the file from the head to the bottom of the page.

Then I removed the 3 script tags from source/_includes/head.html:

Remove script tags
1
2
3
<script src="{ { root_url } }/javascripts/modernizr-2.0.js"></script>
<script src="{ { root_url } }/javascripts/ender.js"></script>
<script src="{ { root_url } }/javascripts/octopress.js" type="text/javascript"></script>

And I added the following to source/_includes/custom/after_footer.html:

Loading combined all.js
1
<script src="{ { root_url } }/javascripts/all.js" type="text/javascript" />

The load time did not change: still around 2.8 seconds. However something broke: the sidebar fails to load my recent tweets because that code depends on some that were moved to the bottom of the page (as explained above). I fixed the dependency and merged all the twitter JS code into all.js. The twitter JS code can be found in source/_includes/asides/twitter.html:

Removing Twitter code
1
2
3
4
5
6
<script type="text/javascript">
  $.domReady(function(){
    getTwitterFeed("jphpsf", 5, false);
  });
</script>
<script src="{ { root_url } }/javascripts/twitter.js" type="text/javascript"> </script>

Disclaimer: this last change might need to be applied to other plugins or sidebar modules. Twitter is the only one I used and after this last change, my recent tweets were back.

Trying to improve the load time further, I decided to load the all.js file asynchronously. The blog content does not depend on any javascript, so it is ok to defer the load to later. I used the same pattern as the Google Analytics snippets, eg. in source/_includes/custom/after_footer.html I now have:

Loading all.js asynchronously
1
2
3
4
5
6
7
<script type="text/javascript">
(function() {
  var all = document.createElement('script'); all.type = 'text/javascript'; all.async = true;
  all.src = '{ { root_url } }/javascripts/all.js';
  s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(all, s);
})();
</script>

This last change brings the DOM ready event to now fires at 0.8 seconds.

Brandon Mathis (the creator of Octopress) mentioned on Twitter his plans to integrate a JS preprocessor (in the same way than Octopress uses compass for CSS preprocessing). I would hope this could include the optimizations mentioned above.

After this change:

  • DOM ready fires at about 0.8 seconds (improved by 0.4 seconds).
  • The page is fully loaded between 2.8 and 3 seconds (no changes).
  • There are 17 HTTP requests for a total of 237KB (saved 4 requests and 7KB).

Optimizing @font-face

Octopress includes the PT Sans font as a default and is including it on the page using @font-face. I noticed a flash of invisible content (see screenshot below). The issue is even more noticeable on mobile devices, which tends to be slower. This is something I noticed on other websites using @font-face as well (Octopress blogs or not).

Paul Irish explains the problem in this 2009 blog post. The flash of invisible content is actually here to avoid a flash of unstyled content (at least for WebKit browsers). When using @font-face, the browser needs to download the font file. If the file arrives late during the page load, then the browser first shows the content without the font applied and switches to using the font as soon as it arrives. This is called a flash of unstyled content (FOUC for short). To work around this issue, WebKit engineers make the content text invisible until the font is loaded. This is better than the unstyled flash, but it is still an issue. To minimize it, the key is to load the font as fast as possible. Removing the @font-face all together is a solution (the fastest font is the one you don’t have to load) but for now I wanted to keep using a custom font, so I started digging.

The first thing I discovered was that I was actually loading 2 fonts (Ubuntu Condensed for the headers and PT Sans for everything else). That’s 25KB and 59KB. One was loaded from my domain the other from Google’s webfont CDN. I removed the Ubuntu font as PT Sans was looking just fine for the headers. Also, PT Sans leverages Google’s webfont CDN. The load time dropped to 2.2 seconds. The DOM ready event fires now at 0.6 seconds. Great! Can we make it even faster?

Looking at my waterfall revealed that the font was loaded late after the DOM ready event. The font download finished at around 1.6 seconds. I am assuming that means the content is invisible until then (more on that later).

Paul Irish’s blog post mentioned another technique: inline the font in the CSS file using data URI. This is a good idea because it removes 2 extra requests (Google’s webfont CDN loads one CSS file and then the font file). Also, a CSS file can be gzipped which can lead to additional savings.

I removed the font face include tags from the source/_includes/custom/head.html and added the following to the sass/custom/_styles.scss file (note: I did not include the actual base 64 content of the font).

Optimized @font-face syntax
1
2
3
4
5
6
7
@font-face {
  font-family: 'PT Sans';
  font-style: normal;
  font-weight: normal;
  src: local('PT Sans'), local('PTSans-Regular'),
    url('data:application/x-font-woff;base64,[BASE64 FONT CONTENT GOES HERE]') format('woff');
}

DOM ready happens slightly later at 0.8 sec but at this point the font is downloaded. Previously the font was ready at 1.6 seconds. This should improve the flash of invisible content problem.

Are we done with @font-face? No. I noticed an issue in Internet Explorer where the font seems to never load. It turned out to be a great hint for the next step. Data URI length is limited to 32KB in IE8 (according to caniuse.com). Could I find a smaller font? A quick search for PT Sans lead me to Font Squirrel which had the same font as Google webfont. However, it was smaller: 18K instead of 59K. I am not sure where that difference came from. The only difference I could see was visually: while very similar, the Google’s webfont seemed to be more condensed than the Font Squirrel equivalent. In the end, I think the font is fine so I went ahead and added it to the CSS file.

To verify the impact of these optimizations we can use the comparison feature of WebPagetest which includes a film strip view as well as a video comparison service. The film strip screenshot below shows that the optimized version at the bottom, as expected, loads about 1 second faster:

You can view WebPagetest video comparison by following this link.

After this change:

  • DOM ready fires at about 0.7 seconds (improved by 0.1 seconds).
  • The page is fully loaded in about 2 seconds (improved by 1 second).
  • There are 15 HTTP requests for a total of 172KB (saved 2 more requests and 65KB).

Optimizing with Cloudflare

Another thing I wanted to investigate was CloudFlare. I am running this blog behind CloudFlare’s security and performance proxy and was wondering about how the various settings could affect the load time and if CloudFlare was helping my blog to load faster.

I’ve enabled the performance proxy of CloudFlare since day one, so this blog should already benefit from CloudFlare optimizations. To determine the gains, I decided to disable the performance proxy and measure the page speed. I lost about 0.5 seconds on my load time doing so. The main difference appeared to be the size of downloaded content. It grew from 172KB to 248KB. The difference comes from gzipping/minifying assets such as CSS or Javascript. When bypassing the performance proxy, I hit Heroku’s server directly and because I haven’t configured Heroku to gzip or minify assets, it serves unoptimized content. CloudFlare’s performance proxy shaves 0.5 seconds off of the load time, that’s not bad.

WebPagetest pointed out that I was not setting the far expire header properly for my assets. CloudFlare actually takes care of that. The default value is pretty low, it is set to 4 hours. I tweaked it to use the recommended 30 days which improved my caching score on WebPagetest.

Another benefit is the CDN: the assets of my blog are deployed on CloudFlare’s CDN. This is totally transparent to me (I did not have to make any changes to my blog’s code) and is great for performance. The CDN stores content closer to the user hence allowing faster delivery. I am running out of time here, but I guess the impact could be verified with Webpagetest. One can pick various test locations and compare the performance with and without CDN.

The last feature I played with is Rocket Loader. It’s a pretty clever Javascript loader. From what I’ve seen it speeds up a website in two ways. First it makes any javascript files load asynchronously. Second it caches them in the browser’s local storage. Unfortunatelly, when I activated the feature, the comments powered by Disqus stopped working. Rocket Loader is currently under beta, so I guess there are a few things still left to iron out. In the end, I’ve already done some optimization to my blog’s Javascript (previously explained) so I disabled Rocket Loader.

Conclusion

I started with 21 HTTP requests for a total of 244KB and a page loading in about 3 seconds. After optimizations, the page is down to 15 requests for a total of 172KB. The page now fully loads in about 2 seconds.

This experiment was very interesting. The big take away was learning more about @font-face performance implications. I think barebone Octopress out of the box performance is not bad at all: the load time of my blog prior to the changes was decent. Yet, I was able to squeeze a little more speed with a few simple changes. I will contact Brandon Mathis (the creator of Octopress) to see if he would be interested by some of these tweaks. With Octopress being open source, patches are as easy as a pull request ;)

Now it’s your turn! Do you have an Octopress blog? How is your load time? If you try the above tips, do you see any improvements? Do you have any other tips that I might have missed? Contact me via the comment form below, Twitter or the Hacker News thread I’d love to hear from you :)

Update September 2012

After weighting the pros and cons about the usage of @font-face, I’ve decided to remove the custom font I was using. In the end, I value speed more than fancy typography. A basic font like Helvetica is perfectly fine for this blog :)

Possibly related posts

Comments