Read more

Rails asset pipeline: Why relative paths can work in development, but break in production

Tobias Kraze
June 08, 2012Software engineer at makandra GmbH

The problem

When using the asset pipeline your assets (images, javascripts, stylesheets, fonts) live in folders inside app:

app/assets/fonts
app/assets/images
app/assets/javascripts
app/assets/stylesheets
Illustration web development

Do you need DevOps-experts?

Your development team has a full backlog? No time for infrastructure architecture? Our DevOps team is ready to support you!

  • We build reliable cloud solutions with Infrastructure as code
  • We are experts in security, Linux and databases
  • We support your dev team to perform
Read more Show archive.org snapshot

With the asset pipeline, you can use the full power of Ruby to generate assets. E.g. you can have ERB tags in your Javascript. Or you can have an ERB template which generates Haml which generates HTML. You can chain as many preprocessors as you want.

When you deploy, Rails runs assets:precompile which precompiles all assets into static files that live in public/assets. This way you have all the performance of static files with all the expressiveness of Ruby.

Unfortunately, the path of every single file changes during precompilation. This means that you have to be a bit careful how you reference assets.

.tile
  background-image: url('../images/foo.png')

won't work any longer, because there is no public/assets/images. foo.png now lives directly in public/assets.

Example

app/assets/fonts
app/assets/fonts/fonts_root.css
app/assets/fonts/fonts_root.js
app/assets/fonts/fonts_root.png
app/assets/fonts/fonts_root.ttf
app/assets/fonts/subfolder
app/assets/fonts/subfolder/fonts_subfolder.css
app/assets/fonts/subfolder/fonts_subfolder.js
app/assets/fonts/subfolder/fonts_subfolder.png
app/assets/fonts/subfolder/fonts_subfolder.ttf
app/assets/images
app/assets/images/images_root.css
app/assets/images/images_root.js
app/assets/images/images_root.png
app/assets/images/images_root.ttf
app/assets/images/subfolder
app/assets/images/subfolder/images_subfolder.css
app/assets/images/subfolder/images_subfolder.js
app/assets/images/subfolder/images_subfolder.png
app/assets/images/subfolder/images_subfolder.ttf
app/assets/javascripts
app/assets/javascripts/application.js
app/assets/javascripts/javascripts_root.css
app/assets/javascripts/javascripts_root.js
app/assets/javascripts/javascripts_root.png
app/assets/javascripts/javascripts_root.ttf
app/assets/javascripts/subfolder
app/assets/javascripts/subfolder/javascripts_subfolder.css
app/assets/javascripts/subfolder/javascripts_subfolder.js
app/assets/javascripts/subfolder/javascripts_subfolder.png
app/assets/javascripts/subfolder/javascripts_subfolder.ttf
app/assets/stylesheets
app/assets/stylesheets/application.css
app/assets/stylesheets/stylesheets_root.css
app/assets/stylesheets/stylesheets_root.js
app/assets/stylesheets/stylesheets_root.png
app/assets/stylesheets/stylesheets_root.ttf
app/assets/stylesheets/subfolder
app/assets/stylesheets/subfolder/stylesheets_root.png
app/assets/stylesheets/subfolder/stylesheets_subfolder.css
app/assets/stylesheets/subfolder/stylesheets_subfolder.js
app/assets/stylesheets/subfolder/stylesheets_subfolder.ttf

This precompiles into this (fingerprinted and compressed files removed for clarity):

public/assets/application.css
public/assets/application.js
public/assets/fonts_root.png
public/assets/fonts_root.ttf
public/assets/images_root.png
public/assets/images_root.ttf
public/assets/javascripts_root.png
public/assets/javascripts_root.ttf
public/assets/stylesheets_root.png
public/assets/stylesheets_root.ttf
public/assets/subfolder
public/assets/subfolder/fonts_subfolder.png
public/assets/subfolder/fonts_subfolder.ttf
public/assets/subfolder/images_subfolder.png
public/assets/subfolder/images_subfolder.ttf
public/assets/subfolder/javascripts_subfolder.png
public/assets/subfolder/javascripts_subfolder.ttf
public/assets/subfolder/stylesheets_root.png
public/assets/subfolder/stylesheets_subfolder.ttf

What has happened?

  1. All asset folders (app/assets/images, app/assets/stylesheets, app/assets/fonts, etc.) are merged into one folder: public/assets.

  2. Subfolders are also merged into public/assets. That means both app/assets/javascripts/subfolder/* and app/assets/stylesheets/subfolder/* now live in public/assets/subfolder/*

  3. All CSS files are concatenated into one file (public/assets/application.css). With the default manifest in app/assets/stylesheets/application.css, stylesheets in app/assets/stylesheets/**/*.css are concatenated. So public/application.css has the following content:

    /* content from app/assets/stylesheets/stylesheet_root.css */
    /* content from app/assets/stylesheets/subfolder/stylesheet_subfolder.css */
    
  4. All Javascript files are concatenated into one file in root (public/assets/application.js). With the default manifest in app/assets/javascripts/application.js, Javascripts in app/assets/javascripts/**/*.js are concatenated. So public/application.js has the following content:

    /* content from app/assets/javascripts/javascripts_root.js */
    /* content from app/assets/javascripts/subfolder/javascripts_subfolder.js */
    

What will break after precompilation?

  • Stylesheets, that used url(../images/foo.png)-style tags
  • @font-face definitions that used url('fonts/bar.ttf')-style tags

How can I fix my stylesheets?

  • Do not use url(../images/foo.png).
    This is plainly wrong, since from your browser's point of view, your stylesheet lives at /assets/application.css and your image at /assets/foo.png. I have no idea why it works in development in the first place. Simply use the SASS helper:

    .tile
      background-image: image-url('foo.png')
    

Managing 3rd party assets

You don't want to manually fix references in external libraries you put into your application. The card Managing asset libraries offers two solutions.

Tobias Kraze
June 08, 2012Software engineer at makandra GmbH
Posted by Tobias Kraze to makandra dev (2012-06-08 13:36)