Untangling the Rails Asset Pipeline, Part 2

Making it work in production.

In the first article of this series I reviewed some issues you may run across while developing a Rails application using the asset pipeline. Today let’s look at what happens in production.

This article is the second of a four-part series on the Rails asset pipeline. Read the previous installment: Part 1: Caches and Compass.

Understanding why assets:precompile exists and how assets are served in production

An out-of-the-box Rails app will not work in production unless you run bundle exec rake assets:precompile first. You probably knew that already. But what does this rake task actually do, and why is it needed? The asset pipeline guide has a good explanation. Even if you’ve read the guide already, a quick refresher won’t hurt.

The entire point of assets:precompile is to completely remove Rails from the equation when assets are served to browsers in production. Rails does all the hard work of compiling assets during this rake task, and stages the finished products in public/assets where the web server can see them. When browsers request CSS, JavaScript and image assets, the web server does what it does best: it serves those static files directly. Rails won’t be consulted at all.

Furthermore, the production settings for Rails are configured in such a way that Rails cannot serve assets without a web server. Again, this in the best interest for performance. By disabling all of the asset pipeline-related code, Rails uses less memory and can process non-asset requests faster. But, it means that if you don’t precompile, or if you aren’t using a web server, your app simply won’t work.

What happens during assets:precompile To summarize, rake assets:precompile carries out these steps:

  1. Compile, merge and minify everything referenced in application.js and application.css. Place the results in public/assets. Image assets are copied over as well.
  2. Generate a cache-buster based on each file’s MD5 checksum and put it in the filename: e.g. application.css becomes application-de9f…05b4.css.
  3. Create a gzipped version of each file: e.g. application-de9f…05b4.css.gz
  4. Generate public/assets/manifest-e4aa7…86f4.json.

The manifest.json is very important: it contains key/value mappings that match each asset name to its MD5 cache-busted name. This allows Rails to look up the cache-busted filename at runtime without recompiling or re-checksumming.

What happens when the browser requests a page. Okay, the assets are all precompiled, and the manifest.json is ready to go. Your app is up and running, and a request for the home page arrives. Now what?

  1. The browser makes the request for the home page:

    GET /
  2. The web server (let’s assume Nginx) hands off the request for / to your Rails app, where it routes to the appropriate controller, your application logic is invoked, and a view is rendered. So far this is straightforward Rails.

  3. Your view requests that Rails render a stylesheet <link> tag for application.css like this:

    <%= stylesheet_link_tag "application" %>
  4. Rails looks up application.css in public/assets/manifest-e4aa7…86f4.json and finds the cache-busted filename. It renders:

    <link rel="stylesheet" href="/assets/application-de9f692fe73ec6663618e53a66d805b4.css"/>
  5. The browser requests the stylesheet:

    GET /assets/application-de9f692fe73ec6663618e53a66d805b4.css
  6. Nginx looks in public/assets, finds application-de9f…05b4.css.gz and serves that file directly. As is appropriate for cache-busted files, Nginx applies far-future expires headers to the response:

    Cache-Control: max-age=315360000, public
    Connection: close
    Content-Encoding: gzip
    Content-Length: 14496
    Content-Type: text/css
    Date: Fri, 10 Feb 2012 05:09:40 GMT
    Expires: Thu, 31 Dec 2037 23:55:55 GMT
    Last-Modified: Tue, 07 Feb 2012 21:52:35 GMT
    Server: nginx/0.8.54

Note how Rails is bypassed completely when serving the CSS file.

Remember to configure Nginx

You’ll notice that Nginx has to do a lot of heavy lifting in order for the assets to be served correctly in production. And in fact, this behavior is not automatic. You need to explicitly configure Nginx to get the best results out of the asset pipeline in production.

Specifically you need to tell Nginx to do two things:

Refer to section 4.1.1 of the asset pipeline guide for specific instructions on properly configuring Nginx.

Continue reading part 3 of this series: Configuration.