I ran across an issue in production recently where my Rails app assets weren’t being served correctly through the Asset Pipeline. It brought back to my attention the fact that I didn’t really understand how all of the Asset-Pipeline-related configuration settings work together.

I got so frustrated that I decided to write out a truth table and test out all possible combinations, and I’d like to share my findings. Hopefully this will help you better understand error messages you may receive, and maybe even show you a setup you’d like to use.

Here’s the tl;dr for if you’re getting a certain error:

  • If you’re getting a 404 for assets at an /assets/ path, make sure you have config.serve_static_files = true, or that you are running inside a web server that will serve these files.
  • If you’re getting a 404 for assets at a /stylesheets/ or /javascripts/ paths, this is because you’ve set Rails not to compile assets at runtime (config.assets_compile = false), and some other settings are incorrect. If you want assets to be precompiled, make sure all your settings match row 6 above.

Here are the settings I investigated:

  • config.assets.compile: allows Rails to generate assets at runtime.
  • config.assets.debug: disables concatenating files, instead creating separate <link> and <script> tags for each.
  • config.assets.digest: appends fingerprints to asset URLs to bust cache.
  • config.serve_static_files: allows Rails to serve up files in /public/, including precompiled assets.
  • Running rake assets:precompile: generates assets at build time and stores them in /public/.

Here are the effects of enabling and disabling these settings in different combinations, as to whether the asset loads (HTTP status 200) or can’t be found (404):

# compile debug digest serve precompile status path(s)
1 true true true (either) (either) 200 /assets/jquery.self-FINGERPRINT.js?body=1
/assets/jquery_ujs.self-FINGERPRINT.js?body=1
/assets/turbolinks.self-FINGERPRINT.js?body=1
/assets/application.self-FINGERPRINT.js?body=1
2 true true false (either) (either) 200 /assets/jquery.self.js?body=1
/assets/jquery_ujs.self.js?body=1
/assets/turbolinks.self.js?body=1
3 true false true (either) (either) 200 /assets/application-FINGERPRINT.js
4 true false false (either) (either) 200 /assets/application.js
5 false true (either) (either) (either) 404 /javascripts/application.js
6 false false true true true 200 /assets/application-FINGERPRINT.js
7 false false true true false 404 /javascripts/application.js
8 false false true false true depends /assets/application-FINGERPRINT.js
9 false false true false false 404 /javascripts/application.js
10 false false false (either) (either) 404 /javascripts/application.js

A few observations:

  • If you have config.assets.compile = true, every other combination of settings works. config.serve_static_files and rake assets:precompile don’t make a difference because the application will always fall back to compiling the assets on the fly. But you can change config.assets.debug and config.assets.digest to control concatenation and fingerprinting, respectively.
  • If you have config.assets.compile = false, there are only two combinations of settings that work. config.assets.debug must be set to false, config.assets.digest must be set to true, and you must run rake assets:precompile. Your setting for config.serve_static_files is the only one that can vary, and the setting you want depends on how you’re running Rails. If it’s running behind a web server like Apache or Nginx, you can set config.serve_static_files to false, because the web server will serve up those files. If Rails is not running behind a web server, you will need to set config.serve_static_files to true, so that Rails will serve these files.
  • Note that Heroku precompiles your assets if it doesn’t already see the compiled assets committed to Git. If you move off of Heroku to another host, you may need to start precompiling yourself.

If you’ve seen the Asset Pipeline behave differently or there’s another setting I should be testing against, let me know!