Faster Sites Done Faster

August 12th, 2009

Google wants your site to be faster. They want it so much, they made videos! You don’t want to watch videos though, you want to make your site faster, faster. Take an hour of time and make your website 4-5x faster using these 5 high-impact techniques:

  • Set HTTP Cache Headers
  • Gzip Web Server Output
  • Use Multiple, Cookie-less Domains for Assets
  • Bundle Javascript & CSS
  • Crush Image Assets

I’m going to use Rails for these examples, but you should have something comparable in any web framework. Rails got a bad rap on speed for a long time, but where the server-side code failed, the architecture won. HTTP and Rails are already in bed together, here’s how to join in.

Set HTTP Cache Headers

HTTP is your friend. Rails already handles adding timestamps to your assets URLs (that’s the ?19438273834 after the image URI. That’s why you always use image_tag), but you need to set up the HTTP headers yourself. In Apache 2 this looks like:

1
2
3
4
5
6
7
8
9
10
<VirtualHost *:80>
  # Your config...
  ExpiresActive On
  <FilesMatch "\.(ico|gif|jpe?g|png|js|css)$">
          ExpiresDefault "access plus 1 year"
          Header unset ETag
          FileETag None
          Header unset Last-Modified
  </FilesMatch>
</VirtualHost>

In Nginx:

1
2
3
4
5
6
server {
  # Your config...
  location ~* (css|js|png|jpe?g|gif|ico)$ {
    expires max;
  }
}

A warning to the wise- this works fairly seamlessly under Rails, but if you are on another framework be sure you have some kind of rolling argument to assets for new deploys. If not, the expires max will mean browsers will not try to pull down the new content you just deployed.

Gzip Web Server Output

Web pages, Javascript, and CSS are all text, and compress very nicely under gzip. This is a no-brainer to turn on, and it will have an immediate impact on your site. In Apache 2:

1
2
3
4
5
6
7
<VirtualHost *:80>
  # Your config...
  AddOutputFilterByType DEFLATE text/html text/plain text/xml application/xml application/xhtml+xml text/javascript text/css application/x-javascript
  BrowserMatch ^Mozilla/4 gzip-only-text/html
  BrowserMatch ^Mozilla/4\.0[678] no-gzip
  BrowserMatch \\bMSIE !no-gzip !gzip-only-text/html
</VirtualHost>

In Nginx:

1
2
3
4
5
6
7
8
server {
  # Your config...
  gzip             on;
  gzip_min_length  1000;
  gzip_proxied     expired no-cache no-store private auth;
  gzip_types       text/plain application/xml text/css application/javascript;
  gzip_disable     msie6;
}

Both of these configurations bypass IE6. That’s a darn shame, if only we could make IE6 load pages faster somehow…liiike….

Use Multiple, Cookie-less Domains for Assets

This technique requires you to update your DNS entries, but it is well worth the effort. There are 2 Rails-side parts to this. One is to always use image_tag, javascript_include_tag, etc. Don’t write your own tags. Then update your asset_host configuration:

1
2
3
4
5
6
7
8
# In config/environments/production.rb
ActionController::Base.asset_host = Proc.new { |source, request|
  if request and request.ssl?
    "https://www" # Just use one domain during SSL.  This avoids mixed content errors.
  else
    "http://www#{source.hash % 4}"
  end + ".coolapp.com"
}

Now configure A records for each asset domain:

1
2
3
4
www0.coolsite.com 12.34.56.78
www1.coolsite.com 12.34.56.78
www2.coolsite.com 12.34.56.78
www3.coolsite.com 12.34.56.78

Lastly, configure your server. The basic configuration (again, Apache 2):

1
2
3
4
5
6
7
8
9
<VirtualHost *:80>
  ServerName www.coolsite.com
  ServerAlias www0.coolsite.com
  ServerAlias www1.coolsite.com
  ServerAlias www2.coolsite.com
  ServerAlias www3.coolsite.com
  
  # rewrite rules, etc
</VirtualHost *:80>

Of course, that leaves the whole app available on any of those domains. We can use a require condition before routing to the app so only the assets are available:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<VirtualHost *:80>
  ServerName www.coolsite.com
  ServerAlias www0.coolsite.com
  ServerAlias www1.coolsite.com
  ServerAlias www2.coolsite.com
  ServerAlias www3.coolsite.com
  
  # other config...

  # Redirect all non-static requests to cluster
  RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
  RewriteCond %{HTTP_HOST} ^www.coolsite.com$ # Only if they are asking for www
  RewriteRule ^/(.*)$ balancer://coolsite_cluster%{REQUEST_URI} [P,QSA,L]
</VirtualHost *:80>

Set up multiple asset hosts now! If you have not done it, this is the most important technique on this page. It will make your website 3-4 times faster in most browsers. Yes, even Firefox will feel faster. Go, do it!

Bundle Javascript & CSS

Fewer downloads means less wait time for the browser. Bundle your Javascript and CSS into a single file for production. Super easy in Rails:

1
2
<%= javascript_include_tag 'mootools.js', 'lightbox.js', 'application.js', :cache => 'cache-application' %>
<%= stylesheet_link_tag 'lightbox.css', 'application.css' :cache => 'cache-application' %>

Crush Image Assets

Ok, this is the hardest thing on this list, and my example is the most Rails specific yet. You will need to install two applications: pngcrush and jpegtran. Both were easily installable in Gentoo, so check Ports or Apt or what-have-you. Now add this file to your project:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# lib/tasks/assets.rake
namespace :assets do

  desc "Crush png images"
  task :crush_pngs do
    Dir['public/**/*.png'].each do |file|
      `pngcrush -rem alla -reduce -brute "#{file}" "#{file}.crushing"`
      `mv "#{file}.crushing" "#{file}"`
    end
  end

  desc "Crush jp(e)g images"
  task :crush_jpgs do
    ( Dir['public/**/*.jpg'] + Dir['public/**/*.jpeg'] ).each do |file|
      `jpegtran -copy none -optimize -perfect -outfile "#{file}.crushing" "#{file}"`
      `mv "#{file}.crushing" "#{file}"`
    end
  end

  desc "Crush images"
  task :crush_images do
    %w( assets:crush_pngs assets:crush_jpgs ).each do |task|
      Rake::Task[task].invoke
    end
  end

end

Now you can

1
rake assets:crush_images

Review the changes, commit and push it live. These utilities are loss-less; They only strip fatty metadata not needed by browsers. Run it every time you make a large number of image changes.

Be Fast & Prove It

Before you start using these techniques, check out Firebug, Yslow, and Google Page Speed. Run the excellent IE focused WebPagetest. Gather some hard numbers before you make a change, then do some follow-up tests. Be amazed. Be fast. Get back to building your great site.

Do you have a dead simple, quick and high-impact web technique? Share it with us!

By Matthew Beale
2 comments
  1. roger rubygems at August 20th, 2009 at 12:15 PM

    Nice links! mod rails really should do this for you, at least I think so. Regardless, another tip is to use google as your distributor for your JS [i.e. prototype] and to use a compactor for your javascript/css. Though I suppose gzip would already be helping there.

  2. Bill Adams at October 6th, 2009 at 06:51 PM

    Hey, nice tip with the “Use Multiple, Cookie-less Domains for Assets”. Spreading multiple requests across HTTP hosts for parallel loading makes a significant impact in overall page load times.

Comments are closed for this article.