Skip to content

Huddle ~ Work Better Together

Huddle Blog

Cache & version your CSS/JavaScript

Wednesday, May 14, 2008

Comments closed

Recently we’ve been working on improving the caching within the website to improve performance for the user. After reading around, we found the following rules to be valuable.

  • Cache your CSS/JavaScript forever (set a long, long expiry)
  • Handle updates by changing the URL of your CSS/JavaScript

When caching “forever” the main problem is if you release an update to “styles.css” the browser caches the URL of that file, but won’t pick up any future updates unless the user clears their browser cache (we’re often telling people to “Ctrl + F5”). By changing the URL of the file, the browser thinks it’s requesting a new file, and your updates get applied. But how do you go about doing this without it becoming a pain to manage?

One common solution we found was to add a querystring to the url for the CSS/JavaScript. For example:

\webresources\css\styles.css?version=3

This is commonly used, but not 100% effective. According to the HTTP caching specification, the user agent should cache a URL ignoring the querystring. Carl Henderson goes into more detail about it in his article, Serving JavaScript Fast. So, according to the specification, “styles.css?version=3” is no different from “styles.css” as “?version=3” is ignored.

Firefox and IE ignore this specification, so using a querystring will still work. However, browsers such as Opera and Safari do do apply the HTTP caching specification, so using a querystring won’t work.

Anyway to be safe, we took a different approach. Using the C# codebehind of our MasterPage we read the LastWriteTime of the styles.css file and include this in the filename. For example:

<link id="CssMerged" href="/webresources/css/styles.{0}.css" media="screen, projection" rel="stylesheet" type="text/css" runat="server" />

/webresources/css/styles.{mmhhddMMyy}.css
/webresources/css/styles.1220010108.css

Here’s a snippet of the C# code used:

protected override void OnPreRender(EventArgs e)
{
    CssMerged.Href = string.Format(CssMerged.Href, GetVersion("/webresources/css/merged.css", "CssVersion"));
}
protected string GetVersion(string filePath, string cacheName)
{
    FileInfo file = new FileInfo(Server.MapPath(filePath));
    string date;
    if (HttpRuntime.Cache[cacheName] == null)
    {
        if (file != null)
        {
            date = file.LastWriteTimeUtc.ToString("mmhhddMMyy");
            HttpRuntime.Cache.Add(cacheName, date, null, DateTime.Now.AddMonths(3), TimeSpan.Zero, System.Web.Caching.CacheItemPriority.Normal, null);
        }
        else
        {
            date = "0000000000";
        }
    }
    else
    {
        date = HttpRuntime.Cache[cacheName].ToString();
    }
    return date;
}

Using Ionic’s ISAPI Rewrite Filter we have a simple rule to ignore the “.1220010108” part of the URL request. So that styles.1220010108.css points to styles.css.

RewriteRule ^/webresources/css/(.*).([0-9]{10}).css /webresources/css/$1.css [I]
RewriteRule ^/webresources/javascript/(.*).([0-9]{10}).js /webresources/javascript/$1.js [I]

We could write a more generic rule, but for now the above is fine. This was easy to apply as we only have one file for our CSS and one for our JavaScript. For developing we keep all of our CSS and JavaScript in separate files. However, when performing a release build we merge all of the CSS/JavaScript into one file each to reduce the number of requests needed in a single page load. We then compress and validate it using YUICompressor. More on that in another post though.

Moving forward, we may move to IIS7 eventually. In doing so it would mean we could implement something such as Url Rewriter (rather than Ionic’s ISAPI Rewrite Filter) within the web application to handle the rewriting.

Also generating the name using the LastWriteTime isn’t ideal. We may look into injecting the version number into the Web.Config when the build is done using CruiseControl.Net.

The main disadvantage to using an ISAPI filer (with IIS6) is that if it falls over, your site isn’t going to have any CSS or JavaScript. We’ve actually had this happen so are stepping carefully with the current solution. If it does pose a problem, again using CruiseControl.Net we could potentially rename styles.css to styles.xxxxxxxxxx.css, and include xxxxxxxxxx in our Web.Config. More on that in the future though.

Tags: huddle, development

Posted by Robert

Comments are closed.