Optimizing website performance on Ruby on Rails

May 19, 2017Engineering7 minute read

Speeding up your marketing website has a positive impact on customer experience and search ranking. That’s exactly what I did this week and in this post I’ll show you how!

This week I spent some time optimizing our marketing website, aiming for a Google PageSpeed rank of 100. Why? Well, a fast website performs better in search results, especially on mobile. Performance is also a critical part of a great user experience—a fast website increases trust and confidence, and reduces frustration. Potential customers are more likely to keep clicking around and sign up if the website is snappy.

Our marketing site runs Ruby on Rails on Heroku. This is a holdover from Dovetail 1 which was built entirely on Rails. We’ve since moved our app to React, but the marketing site is still on Rails and will be the foreseeable future. This post will detail specific Ruby on Rails performance optimizations, however the general concepts apply to any website setup.

The first thing to do is to take a baseline measurement of your production environment. Head over to PageSpeed Insights and enter your URL. After each optimization, check again to make sure you’re improving. We’re currently sitting on a PageSpeed score of 96/100. (I’ll explain why we’re not quite 100 at the end.)

Minify HTML, CSS, JS, and image assets

Minification refers to the process of removing unnecessary or redundant data without affecting how the resource is processed by the browser — e.g. code comments and formatting, removing unused code, using shorter variable and function names, and so on.

The easiest thing to do is to ensure everything is concatenated and minified. The Rails asset pipeline concatenates and minifies JavaScript & CSS by default when it runs in the production environment. It also minifies HTML. If you’re not running Rails, there are plenty of libraries that will concatenate and minify code for you in any language.

The next step is to compress images. I run all our images through ImageOptim at 80% quality with lossy minification enabled. We’re mostly serving SVGs or PNGs @2x where the drop in quality from lossy minification is not noticeable and the file size gains are worth it.

Serve compressed assets

All modern browsers support and automatically negotiate gzip compression for all HTTP requests. Enabling gzip compression can reduce the size of the transferred response by up to 90%, which can significantly reduce the amount of time to download the resource, reduce data usage for the client, and improve the time to first render of your pages.

Rails will serve gzipped HTML, JS, and CSS by default but it won’t serve gzipped SVGs even though it generates .svg.gz files. The solution for now is an unfortunate monkey patch in an initializer that tells Action::Dispatch to serve gzipped SVGs along with other files.


Defer JavaScript

Before the browser can render a page it has to build the DOM tree by parsing the HTML markup. During this process, whenever the parser encounters a script it has to stop and execute it before it can continue parsing the HTML. In the case of an external script the parser is also forced to wait for the resource to download, which may incur one or more network roundtrips and delay the time to first render of the page.

To ensure the browser isn’t waiting for non-critical JavaScript, we can defer it to be executed later with a quick change.


However, if you’re interacting with the DOM, you’ll need to wrap your JavaScript in a listener to wait for it to be fully loaded. Here’s an example of us doing that for Mixpanel.


Leverage browser caching

Fetching resources over the network is both slow and expensive: the download may require multiple roundtrips between the client and server, which delays processing and may block rendering of page content, and also incurs data costs for the visitor. All server responses should specify a caching policy to help the client determine if and when it can reuse a previously fetched response.

PageSpeed complained about some assets having short cache expiry times and some not having expiry times at all. To fix this in Rails on Heroku, we need to add the heroku-deflater gem to our Gemfile which lets us set default headers for assets via a line in our production.rb config file.


Reduce network requests

Our website doesn’t have a lot of CSS, mostly because I removed Bootstrap entirely (see below). Since our total CSS comes in around 5kb, it’s perfect to inline. This saves the browser from making another network request to fetch the CSS externally.

The approach I used was to create a helper which reads the file and inlines it as a <style> tag in the <head> of the document.


In our layout file simply call the helper and remove the <link> stylesheet tag.


Another trick to reduce the number of network requests is to use sprites for collections of images or icons. On our site we have a bunch of icons for the feature callouts and a lot of logos for our integrations section. Both of these are optimized, gzipped SVG sprites. You could also inline SVGs—which we do in our app—but not currently on the marketing website.

Remove jQuery and Bootstrap

Do you really need jQuery or Bootstrap? When we re-wrote Dovetail in React and deprecated Dovetail 1, the need for jQuery & Bootstrap almost went away entirely. All that was left were about 200 lines of JavaScript to do things like add a shadow to the header when the user scrolls. I rewrote that jQuery as plain JavaScript and removed the remaining Bootstrap. The result? Our compressed JS went from 35kb to 2.3kb and our CSS from 20kb to 5kb.

Split the website & app

If your Ruby on Rails app is a monolith (marketing website & product in one), then you probably have a single layout file called something like ‘application’ along with single concatenated JS and CSS files.

The problem here is that anonymous website visitors have to download all the JS and CSS for your app, even though they’re not using it. It’s a good idea to split this code up so everyone downloads only what they need. When users log in, they’ll download the relevant JS and CSS.

Create a new layout file called ‘website’ under views/layouts, then tell the relevant controllers to use that layout instead of the default.


Do the same with your JS and CSS (if you’re not inlining it). Split the website and product JS then include the relevant include tag in each layout file.


Use system fonts

The last piece of advice I have is to ditch custom fonts and go for native system fonts. This is one less network request or file to download. Our font-family stack targets native fonts on OS X, Windows, iOS, and Android.


Wrap up

By now you should have drastically improved your website performance and PageSpeed score. These changes will go a long way to ensuring a better user experience and search ranking for your marketing website.

There’s one problem — you might not quite be at 100. You might find that the remaining things PageSpeed complains about are out of your control. In our case, it’s the short expiry times set by external scripts and a slightly unminified Facebook script.

image 1

I looked into how I could improve this but didn’t find an ideal solution. One option is to download and serve these files ourselves, but then we’d need to set up a job to check them for updates which is not ideal. I’m keen to hear your opinion on this one and any other website optimization tips you have!

Join 10,000+ user researchers

Made in Australia by 🐨Auzzies and 🥝Kiwis

© Dovetail Research Pty. Ltd.

ABN: 84 615 270 025