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
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:
- Compile, merge and minify everything referenced in
application.css. Place the results in
public/assets. Image assets are copied over as well.
- Generate a cache-buster based on each file’s MD5 checksum and put it in the filename: e.g.
- Create a gzipped version of each file: e.g.
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?
The browser makes the request for the home page:
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.
Your view requests that Rails render a stylesheet
<%= stylesheet_link_tag "application" %>
Rails looks up
public/assets/manifest-e4aa7…86f4.jsonand finds the cache-busted filename. It renders:
<link rel="stylesheet" href="/assets/application-de9f692fe73ec6663618e53a66d805b4.css"/>
The browser requests the stylesheet:
Nginx looks in
application-de9f…05b4.css.gzand 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:
public/assetsfiles with far-future expires headers
- Use the precompiled
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.