In Spring 2011, at what would sadly become the final installment of Microsoft’s MIX conference, I attended a great talk called 50 Performance Tricks to Make Your HTML5 Web Sites Faster. I was skeptical about the session due to the linkbait title, but Jason Weber from the IE team proceeded to present one of the most comprehensive talks about web performance I’ve seen crammed into a single hour.
A video of the presentation is still available online as I’m writing this. Streaming seems to be broken, but the full video downloads still work fine. Even three years later, it’s still well worth spending a few minutes to watch his presentation if you’re developing for the web and care about performance.
Out of all the points that Jason covered in his talk, the tip at about 21 minutes into the presentation stood out to me the most: Standardize on File Capitalization Convention.
Why filename casing matters: A few examples
Compared to most other web servers and operating systems, something unique to IIS and Windows is their lack of case sensitivity when it comes to the names of paths and files. As far as they’re concerned,
INDEX.HTML are all the same file, for example.
While case insensitivity may seem harmless and maybe even a little convenient, taking advantage of that flexibility (on purpose or not) can lead to unnecessary cache misses for your users. Worse, the problem isn’t very obvious unless you’re paying close attention.
An initial example: lowercase
I’m going to use this logo from the ASP.NET website as an example since it’s hosted on Windows/IIS and has a reasonably simple URL:
Typing that all lowercase address into a browser window and watching the image download in Chrome’s developer tools looks something like this:
Looking at the response headers, we can see that the server is sending a good far-future expires header for the image, which improves performance on subsequent pages using that same logo image:
That “Expires” response header tells browsers to skip re-requesting the logo image again for nearly six years — not even a ping to check for a 304 response.
Six years of caching is probably overly-optimistic about how long my browser’s cache will remain primed with this image, but it doesn’t hurt to try. Using far-future expires headers to reduce the number of HTTP requests needed to display a page is a crucial part of building fast sites.
Unfortunately, functionally irrelevant variations in the URL can defeat all of that.
Another example: Mixing in some UPPERCASE
The problem with case insensitivity is that it can lead to needless cache misses that aren’t obvious. To illustrate that, let’s make a slightly modified request for the same ASP.NET logo, this time with “NEW” capitalized:
On a case-sensitive server’s filesystem, that request would immediately be bounced with a 404 error response. On IIS, it will resolve to the same image file as before and work just fine.
Unfortunately, the flexibility of case-insensitivity comes at the expense of an unnecessary cache miss in this case. Watching this second request in Chrome’s developer tools makes that much clear:
So much for six years of caching. I didn’t even get six seconds!
Of course, that’s understandable behavior on Chrome’s part. Browsers don’t have a reliable way of knowing that a particular server doesn’t respect case sensitivity and that the two URLs refer to the same asset.
This behavior isn’t limited to Chrome, to be clear. Microsoft’s own Internet Explorer, which you would expect to understand IIS, works the same way when it comes to casing variants. This is just how browsers work.
The Sum of All Abbreviations: Everyone knows it’s UI, not ui
It’s not just variations in the filename that break caching either. What do you think happens if we make a request to the same half-capitalized
asplogo-NEW.png file as in the last example, but with “UI” in the last path segment capitalized?
Here’s the result of requesting that from Chrome’s address bar and watching the developer tools one more time:
Yet another full request for the same image, ignoring the two identical copies of the image that are already cached on disk.
As you can probably already imagine, this can easily get out of hand. Here’s a look into Chrome’s cache after I made a few more of these intentionally unique — but reasonably realistic — requests:
Not only is that image taking up six times the space that it should on the user’s machine (and competing for cache space with other assets), but five of those cache items represent requests that could have been avoided altogether.
Is this a real problem?
On the one hand, the previous examples are contrived. I have no reason to believe this logo image is referenced with inconsistent casing anywhere on the ASP.NET website. All the cache misses shown in screenshots above are purely a result of me typing silly things into my browser’s address bar.
At the same time, none of those variations on the path and filename are unreasonable. I’ve seen all of those types of capitalization used on production sites before, including the uppercase “NEW” suffix. More importantly, I’ve seen them used inconsistently within the same site and causing the kind of unnecessary cache misses illustrated above.
With most current ASP.NET project templates including initial caps folders like
Scripts, filled with all lowercase files, this sort of casing confusion is almost inevitable. In the absence of a conscious effort to standardize on casing, it’s only a matter of time before the cache misses start piling up.
Conclusion: Choose your OWN AdVeNtUrE
Like Jason says in his presentation, the key is to pick a file casing convention and adhere to it strictly throughout your entire site.
Personally, I think the best solution is to standardize on all lowercase paths and filenames. All uppercase is COBOL-noxious, while initial caps is both more keystrokes and sometimes leads to awkward results (e.g. Jquery and Ui).
What do you think? Is this issue something you and your team has considered/addressed? If so, what conventions have you chosen? Does anyone want to mention ETags (I purposely avoided them in this post for a reason)?