1. Increase your asset cache lifetime
  2. Prerequisites
  3. A million files
  4. Cache-Control header
  5. Implementing Cache-Control for static assets
    1. Cache-Control header for Apache
      1. Option 1: mod_expires
      2. Option 2: Header set
    2. Cache-Control header for nginx
    3. Cache-Control header for lighttpd
      1. Option 1: Using mod_expire
      2. Option 2: Using mod_setenv
  6. Result

Increase your asset cache lifetime

Most of a website assets rarely change. Every once in a while you might update an image, so a new version of your sprites has to be generated, you add some CSS rules, or add an additional JavaScript library.

Between these changes, however, sprite images, CSS files and JS files are static - and you should have your users get them only once! First time visitors have to download these files initially and will hence not profit. But the bunch of visitors returning to your site will be pleased to have all assets load instantly, leading to a much improved browsing experience.

“But”, I hear you say, “if my assets live forever, how can I make sure visitors see the newest styles and images?” There’s at least one way to ensure this, and we’ll walk through it in this post.

Moreover I’ll show you how to set appropriate caching headers so your visitors’ browser will know how to deal with your assets.

Prerequisites

If you haven’t done any fancy cache-busting magic on your site, it probably has some asset files like this:

assets/
    css/
        screen.css
    js/
        script.js

We’ll use screen.css as an example, although it applies to scripts.js (and it’s equivalent in your project) in a similar fashion. As you develop your site, you make a lot of changes to screen.css, deploying different versions of it over time.

Version File name
1 screen.css
2 screen.css
3 screen.css

To ensure your users are downloading the current version of your screen.css, you could be using a “hack” like appending the version code when referencing your stylesheet:

<link href="/assets/css/screen.css?version=3" media="screen" rel="stylesheet" />

While it fulfills the purpose of having visitors download the current version of your assets, it largely disallows caching in browsers.

A great way to deal of combining both is to embed any version identifier into the filename:

Version File name
1 screen-1.css
2 screen-2.css
3 screen-3.css

Now, instead of updating your version parameter, you’d have to update the actual filename. While this would work, there is a way I personally prefer: Include a hash representing the file contents into the asset filename.

Version File name
1 screen-s4c9430f26c.css
2 screen-s3fd0578bcc.css
3 screen-sfe970f324a.css

Alright, nobody would ever generate these filenames by hand, so you’ll better let your asset pipeline tool of choice do the job. If you are using Compass you find a step by step guide for doing this here (Compass does this automatically for sprite images, but not for generated CSS).

A million files

Using version-tagged asset files will leave you with a lot of versions for each asset on your hard drive. You may want to clean them up.

When you can get rid of old asset file version largely depends on if any visitor could get a version of your site where this particular asset version is referenced.

If you have absolutely no HTML caching mechanisms in place, you have to keep the latest version of each asset only.

If you have HTML caching mechanisms, these will likely result in delivery of multiple asset versions in parallel. Therefore the highest cache lifetime determins the time you should keep your old asset versions available.

This is a great task for automation. If you followed my compass cache busting guide, you’ll have it in the pipeline already for your CSS.

Cache-Control header

At this point, your site has perfectly versioned asset files. As each version represents one state of your app, and new states will be referenced by new filenames, you can allow that these assets be cached for a looooong time.

I’d say a year is a good fit. What this means is that your visitors returning to your site have to only download asset files

  1. if you modify assets and your site embeds new versions
  2. if they clear their browser cache (or use a different browser, of course)
  3. if they revisit after more than a year

There’s a magic bullet for defining asset lifetime, the Cache-control response header.

It’s syntax for lifetime is pretty simple:

Cache-control: max-age=3600

A visitor’s browser is able to cache a request response sending this header for one hour (3600 seconds).

The official specification of Cache-Control shows several additional parameters available to control cache responses. Two are especially interesting, namely public and must-revalidate:

public

Indicates that the response MAY be cached by any cache, even if it would normally be non-cacheable or cacheable only within a non- shared cache.

must-revalidate

Because a cache MAY be configured to ignore a server’s specified expiration time, and because a client request MAY include a max- stale directive (which has a similar effect), the protocol also includes a mechanism for the origin server to require revalidation of a cache entry on any subsequent use. When the must-revalidate directive is present in a response received by a cache, that cache MUST NOT use the entry after it becomes stale to respond to a
subsequent request without first revalidating it with the origin server. (I.e., the cache MUST do an end-to-end revalidation every time, if, based solely on the origin server’s Expires or max-age value, the cached response is stale.)
[…]

With these two options you basically allow intermediate caches (i.e. a CDN provider) to cache your assets as well for the specified time.

Combining these with a year-long lifetime, your assets should respond with the following Cache-Control header to be cached for one year:

Cache-control: max-age=1314000, public, must-revalidate

Setting this most likely requires access to your web server configuration, or at least an appropriately configurable .htaccess file if you’re running your site on Apache.

Implementing Cache-Control for static assets

The following samples for the major three web servers assure that your Compass-generated static assets have a one year long lifetime.

If you are not using Compass or want to extend these rules to include other assets, you’ll have to change the relevant matching rules.

Cache-Control header for Apache

There are two ways to trigger cache control in Apache.

Option 1: mod_expires

If it is available on your server, mod_expires provides a easy to configure way to set asset lifetime.

# assets/.htaccess
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresDefault "access plus 1 year"
</IfModule>

Place this .htaccess file in your assets directory and your Apache webserver will start delivering all assets within with appropriate headers.

Option 2: Header set

# assets/.htaccess
Header set Cache-Control "max-age=1314000, public, must-revalidate"

Again, placed in the assets directory this marks all responses to be cachable for one year.

Cache-Control header for nginx

Warning: The following configuration is untested.

It should, however, set the lifetime to sometime in 2037 for all files in assets/. Check the [nginx-expires][documentation] and these configuration examples for details.

location /assets {
    expires max;
    add_header Pragma public;
    add_header Cache-Control "public, must-revalidate";
}

Cache-Control header for lighttpd

Option 1: Using mod_expire

    expire.url = ( "/assets/" => "access plus 1 year" )

Option 2: Using mod_setenv

   $HTTP["url"] =~ "^assets/.*" {
      setenv.add-response-header  = ( "Cache-Control" => "max-age=1314000, public, must-revalidate" )
  }

Result

Assets are requested only at first visit, and then cached. Returning visitors will have all your assets readily available without downloading again.