This is Article #4 of a 4-part series. For a good primer, check out the first two articles listed below. For the reasoning and analysis behind the "Recommended" option in this article, check out Part 3, How to Combine GZip + CDN for Fastest Page Loads. Otherwise, jump right in!
The best option for you may depend on your specific needs, but I'll cover both processes below. For a an in-depth analysis of why Option A is recommended over Option B, see the last article in this series, How to Combine GZip + CDN for Fastest Page Loads.
Luckily in Rails, this is easy, simply add
:cache => 'cached-file-name' to your
Now, as long as the following line is set to true in your environment.rb, or more likely in production.rb, Rails will either load your combined files in the layout, or create and load them if they don't already exist.
config.action_controller.perform_caching = true
If you do not yet have an Amazon AWS account, you will need to create that and enable S3 and CloudFront services. See this writeup on creating and setting up an Amazon S3 account. for helpful instructions.
Once you've signed up for your Amazon AWS account and activated S3 and CloudFront, you'll want to setup 4 S3 buckets for your application, using Amazons S3 management console.
We're going to setup 4 buckets and CDN distributions because some old browsers still have an artificial limitation that only allow 2 concurrent connections to each domain, meaning our components will take longer to download from Amazon if they can only be downloaded 2 at a time. By creating 4 different domains pointing to 4 different buckets/distributions, we're allowing our components to download up to 8 at a time from those browsers that still enforce this limitation.
Once your S3 buckets are created, click over to the CloudFront tab and create one distribution for each S3 bucket as shown. You can type any comment to help you quickly identify each distribution.
Once you've created your 4 CloudFront distributions, you may create a CNAME record for each distribution. This allows you to serve files from CloudFront using your own asset subdomains, like cdn0.yourapp.com, instead of raNDomString1234.cloudfront.net. We'll use the following format of
%d stands for digits 0-3:
cdn0.yourapp.com cdn1.yourapp.com cdn2.yourapp.com cdn3.yourapp.com
This plugin adds some Capistrano recipes to synch our application's
public directory with our four S3 buckets automatically every time we deploy our app. See Spatten Design's documentation for more information. I've made some updates to their original plugin to properly set the Cache-control and Expires headers for our assets on S3, as well as to properly set the Content-encoding header for Gzipped assets.
Update: I've updated the S3 Synch Plugin further; it can now handle unique S3 buckets for different Rails environments (e.g. one set of buckets for production and another for staging). Be sure to update your synch_s3_asset_host.yml file as shown below.
The synch_s3_asset_host plugin requires the AWS-S3 gem, so add the following to your
...and then run the following from the terminal to install the S3 Synch plugin's gem dependency:
sudo rake gems:install
config/synch_s3_asset_host.yml file like this:
Update: The "production" part in the file above has been added for my latest update of the S3 Asset Synch Plugin.
Now, in your Capistrano
deploy.rb script, add the following line to the
namespace :deploy do ... before "deploy:symlink", "s3_asset_host:synch_public" ... end
...and then add the
:asset_host_syncher => true flag to the
For more detail about why this method is recommended, see the last article in this series.
Use the following configuration in your
production.rb file to configure the way Rails writes the URLs for asset_tags:
source.hash % 4code above. This ensures that the same component is always served from the same subdomain to take full advantage of client-side caching for that component, rather than randomly selecting from which subdomain to serve each component on each page load.
We will also need to create an A-name record for the cache.yourapp.com subdomain, which points to your application server's IP address.
Now we need to configure Apache to accept incoming requests to our "cache" subdomain, setting the appropriate far-future Expires and Cache-control headers. We also need to tell Apache to automatically compress and serve any compressible filetype on the fly. Add this to your site's Apache
Now we need to make sure the appropriate Apache modules are enabled and restart Apache.
sudo a2enmod deflate sudo a2enmod expires sudo /etc/init.d/apache2 force-reload
For more detail about why this is not recommended, see the last article in this series. Basically, though, it's because it requires you to make one of the following compromises:
That being said, if this is acceptable for you, this method is simpler to set up and configure.
Add the following to your
To solve this, we'll add some Capistrano scripts to our
deploy.rb to compile our files for us before the synch_s3_asset_host plugin copies our
public directory over to our S3 buckets. But this means, we'd have to copy the list of asset files to be compiled into our Capistrano script, as well as having them listed in our
application.html.erb layout. To DRY things up a little, we're going to create some project-wide constants:
And then replace your
stylesheet_link_tag in your application layout with the following:
Add this to your deploy.rb script:
...and then add this to the
:deploy namespace in your
deploy.rb file, before calling the s3_asset_host sync script:
namespace :deploy do ... before "deploy:symlink", "assets:package_cached_assets" before "deploy:symlink", "s3_asset_host:synch_public" ... end
Now simply save your project and deploy it! The first deploy will take quite a while, as your entire /public directory will be copied to all 4 buckets on Amazon S3, one at a time. But after that, it's a painless process.
--excludelist in the synch_s3_asset_host plugin on line 186 of
Whether you chose the "recommended" or the "easier" option, you should immediately notice a significant increase in the performance of your Rails app. Thanks for sticking with me through this 4-part series! Please let me know if you have any thoughts, questions, or feedback in the comments.