Client side optimization for Rails 3.2 on heroku
I would like to share some tips with you on how I optimized my own website using techniques recommended by Ryan Bates of Rails casts followed by my own investigation into the subject. If you want to follow this blog post please familiarise yourself with some of the concepts mentioned in the original railscasts which can be found here
Following on the railscasts, I set about running google pagespeed on my own website, 29steps.co.uk. As always, google pagespeed throws up some common issues which I am already aware of. Here's how I overcome the original list of at least 8 high and medium priority items down to just 4 low priority ones:
High / Medium Priority items as reported by google pagespeed:
Combine images into CSS sprites
This means that google pagespeed has detected several images or icons used on the page but are inserted directly into the HTML tags itself. If these images can be combined into a single image and using css sprites to generate classes based on their background position, this warning would go away.
To fix this I use http://csssprites.com/, which allows me to upload the images concerned and concatenate them into a single css sprite. One cool feature of the site is it even generates the css for you with background positions! With this I was able to clear the warning.
I tried using Compass with SASS but for some reason it will not pick up the images to reference in the css so the manual method works.
You may have to reoptimize the images again - please refer to point 3 below.
Defer parsing of JavaScript
This comes up a lot for me if I use a lot of google plus and other social media javascript embeds in a single page. The plugins for the home page in question that caused this alert are google plus and twitter.
To overcome this issue, I removed the actual javascript embeds from the view template and created a separate js file to autoload the plugins only when the dom is ready, hence 'deferring' the process. The javascript I used is as follows:
This was saved in my assets/javascripts/ folder as 'deferred_main.js' and I use it to load any external javascript files. This is also referenced in application.js so that it can be compiled with it.
This tends to come up on my site regularly after I have added an image to the site. What this means is that the image are not 'lossless' compression and it can be removed by compressing the image before uploading it to the site.
Since I am running imagemagick on my os x, I use the jpegtran command line tool to compress each image to upload:
A more detailed explanation of image optimization can be found below:
http://yuiblog.com/blog/2008/12/05/imageopt-4/
Since I am using S3 to store the image, I just uploaded the compressed image into the right folder path.
If you are using google pagespeed analyzer within chrome it will provide a link for each image that it has optimized for you. I find in most cases the quality is as good as the original so you can actually download and upload it to your server rather than re compressing it again.
Using HTTP conditionals caching
I noticed that despite resolving some of the high / medium issues above, the site is still slow on first time load. I re ran the test on another site, webpagetest.org
The reports shows that the First byte load time falls outside the acceptable range of approx 380ms. Note that this value varies for different sites. The report also pointed out that it is a backend issue
Despite contrary belief, starting up another dyno web process does not help. The response time stays the same so it is an issue with the caching mechanism inside refinerycms.
Since I am using RefineryCMS I know page caching is already in use but I decided to add in http conditional caching by extending the page controller through a decorator object:
What the script above does is to cache the page using the page object etag and last_modified_date based on the page object updated_at date attribute. If the page is the same, it will return a cache status of 'fresh' and fetches it from the cache and returns a 304 status message; else it will call the application backend to fetch the template and store it in the cache.
We also need to override the cache_key method inside another decorator for the Refinery::Page model else you will get errors as the original cache_key method is expecting the locale to be passed as an argument - the decorator simply sets a default of 'en' which can also be changed to read from the Refinery::Configuration based on your locale settings.
The above assumes you are already using Rack::Cache with Dalli on heroku. If not please visit the tutorial below to set it up:https://devcenter.heroku.com/articles/rack-cache-memcached-static-assets-rails31.
If you are running on rails 3.2 serving your assets statically also cuts down on page load and will also eradicate some of the message from google page speed. Since I am already serving static assets, it makes sense to use the same architecture to use http caching.
Another issue to note is if you are on heroku, it uses Varnish as a reverse proxy. You will need to set the fresh_when and expires_in commands with 'public => true' else it will not work as it is marked as 'private' by default.
I am not sure how accurate this is by on webpagetest.org, it is recommended to server all static assets such as images and stylesheets through a Cloud Distribution network. Since I am already using S3 I can do so by adding a CDN to the S3 bucket.
If you are looking for a CDN but do not want the hassle of setting one up, try CloudFlare. The starter package is free to use and it uses the DNS settings you provide to set everything up for you - worth a try
If you are running rails 3.2+, the asset pipeline is enabled by default so your javascripts and stylesheets will be compressed and minified hence bypassing the other google pagespeed messages such as 'minifying javascripts' etc. You need to ensure that you regenerate your assets folder inside public before re deploy.
After making those changes above and running some more tests, my overall page speed score has improved to 1.12 and an overall score of 99/100. Here is a link of the report.
The First byte test is still problematic on webpagetest.org which shows it as failing with just one run and passing with 2 runs. I would need to investigate this further.
I did not optimize the actual javascript calls in this instance.
I hope this helps someone with a similar dilemma on trying to improve page load times with your rails 3.2 application running on heroku or your own vps whether or not you are using refinerycms.