jQuery 1.8 is out, and that new version is available on the Google CDN now.

That’s good news on both counts, but reminds me of an issue that I’ve been meaning to write about for quite a while now. Unfortunately, each new 1.n jQuery release results in a new wave of sites linking to the Google CDN’s copy of jQuery in a way that seems intuitively correct, but results in needlessly poor performance.

If you don’t care about the hows and whys, the short story is that it’s crucial that you always specify the full major.minor.patch version number when you use the Google CDN. Even though jQuery itself only refers to its new releases with a major.minor version number (e.g. 1.8), it’s important that you append a trailing .0 when you use the CDN to include a new minor revision on your page.

In other words, please use this URL to reference jQuery 1.8 on the Google CDN:

ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js

Under no circumstance should you omit that seemingly superfluous .0 in the version number on a production site. Failure to include the trailing zero will result in significantly degraded caching.

If you don’t care about why, you can stop here. Just be sure to add the .0 when you’re referencing jQuery 1.7, 1.8, 1.9, 2.0, 2.1, and so on. If you’re interested in why it’s so important, keep reading.

My least favorite Google CDN feature

One of the Google CDN’s less obvious features is that you can reference the libraries on it without specifying a full version number. If you do that, the CDN will determine what version of the library is the latest one that matches your partial version number and respond with that.

For example, referencing any of the following URLs will retrieve the exact same copy of jQuery 1.8 at the time this was written:

// Obviously...
ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js
 
// Makes sense...
ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js
 
// Living dangerously.
ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js

Similarly, both of these URLs will retrieve the same jQuery 1.7.2 script content, probably for as long as the Google CDN hosts jQuery 1.7.x:

ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js
 
ajax.googleapis.com/ajax.libs/jquery/1.7.2/jquery.min.js

When you first discover this “latest version” feature, it seems clever. Whether you want to keep your site’s jQuery reference up to date automatically or don’t feel like typing the couple extra characters, using the less-specific references may be appealing. However, if you care at all about your site’s performance, you should not use this feature.

Since the abbreviated references return the same content, you may be wondering why it matters whether you use the 1.8 or 1.8.0 URL variation. Caching is the key.

Caching makes the web go ’round (faster)

If you’re interested in web performance, you’ve probably already heard of far-future caching headers as a best practice. In the struggle against lethargic load times, good caching is one of the most potent tools in your arsenal.

As it turns out, that far-future caching mechanism is at the crux of why a fully-qualified version number is important in references to jQuery on the Google CDN. So, it’s worth talking about exactly how that works.

Better than nothing: 304 “Not Modified”

Even without special caching instructions, browsers automatically cache local copies of static files, like jQuery, that they download in the course of rendering pages. To speed up subsequent visits, they then attempt to use those local copies instead of downloading the same file over and over again. However, without any knowledge of how long each file is okay to reuse before it has been updated, the browser needs to check in with the server before it uses that local copy.

When your browser makes a request for static content that it has cached locally, it also adds an If-Modified-Since HTTP header to the request. That header essentially gives the server an opportunity to indicate whether or not the resource has been changed since that timestamp.

If that If-Modified-Since header that the browsers sends is more recent than the file’s last modified timestamp on the server, then the server can return a response of 304 “Not Modified” instead of sending the file back at all. When the browser receives that 304 response, it will then load the file from its local cache instead of retrieving it from the server.

When you’re dealing with large assets, a 304 response can save significant amounts of download time. Unfortunately, with a file as small as jQuery, the ceremony necessary to retrieve that 304 response is nearly as time consuming as just downloading the file in the first place.

Best: Far-future caching

Eliminating the inefficient 304 “Not Modified” handshake is where far-future expires and cache-control headers come into play.

Returning those HTTP headers along with a resource allows you to pre-authorize the browser to reuse that content again for some period of time in the future when it encounters a reference to the same URL. Taking advantage of that mechanism empowers the browser to skip the request for a 304 check entirely and immediately use its cached copy from disk if available.

Even when you apply far-future caching to site-specific content served from your own servers, the impact on a visitor’s repeat page views can be dramatic. Applied to a public CDN, where many sites reference the same URL for popular scripts like jQuery, a far-future expires header means that browsers can go weeks or months between requests for a particular version of the asset.

Too much caching: Not always a good thing

With that performance difference in mind, it obviously makes sense to take full advantage of far-future caching for relatively static content like jQuery.

However, far-future caching isn’t without its drawbacks. The same mechanism that makes it great for performance can also be a troublemaker when you instruct browsers to aggressively cache content that is eventually updated.

Imagine what would happen if your site served up /scripts/jquery.js with an expires header indicating that it was cacheable for one year. That would be great for performance initially, but what happens when a newer version of jQuery is released?

If you replaced that same file with the new version of jQuery when it was released, browsers that had visited your site before the change would continue using their older, cached version of jQuery. Since the whole point of far-future caching is that browsers don’t need to check for a 304 “Not Modified”, browsers with older copies of the file would never know their cache was stale.

On the other hand, visitors with empty caches would download the newer version of the file from your server and then begin caching that revision of it for the one year expires period.

In other words, using a single URL for multiple versions of a well-cached script means you can never be sure that any two given users were viewing your page with the same version of that script. The only solutions are to decrease caching, which impacts performance, or use unique URLs for each version of the script.

So, how does that affect the Google CDN?

As you’re probably realizing, you can’t serve a constantly evolving library like jQuery from a single URL and use optimal expires headers. When you’re serving evolving content through a single URL, you have to make a choice between performance (longer browser cache invalidation) and reliability (quicker expiration so new versions roll out evenly).

Taking a look at the HTTP headers that the Google CDN sends back with the 1.8 and 1.8.0 URLs, you can see that they choose a point dramatically on the reliability end of that spectrum.

“Latest Version”

First, let’s look at the “latest version” reference for jQuery 1.8.x. That’s the most recent minor revision of jQuery as I’m writing this and a reference to 1.8 returns a copy of jQuery 1.8.0.

Looking in the network section of Chrome’s developer tools, these are the HTTP headers returned with a “latest version” request to the CDN:

The HTTP response headers that come back from a request to the 1.8 latest version reference

In an attempt to minimize inconsistencies when a new version (like 1.8.1) rolls out, the 1.8 reference is served with a meager 3,600 second cache expiration. That’s just one hour of caching.

The Cache-Control header returned with the “latest version” response even bolsters that conservative cache timeout with additional revalidate directives. Those directives instruct intermediate agents like caching proxies that they should not attempt to cache this resource more aggressively than stated.

Fully qualified

Now, let’s request exactly the same jQuery 1.8.0 content, but use the fully qualified 1.8.0 URL:

The HTTP response headers that come back from a request to 1.8.0 specifically

Since requests to this URL will never return different content in the future, the CDN safely instructs the browser that it can continue using this same copy of the file for up to 31,536,000 seconds. If you don’t have a calculator handy, that’s one full year, or 8,760 times longer than the “latest version” URL.

Conclusion

One of the greatest strengths of using a popular public CDN to serve jQuery is that your users may already have the library cached as a result of visiting another site that references the same URL your site does. When you use the “latest version” reference, you almost completely eliminate that possibility. The chances of a cross-site cache hit within one year are great. Within one hour? Good luck.

Clever as the “latest version” feature might seem at first, you should never use one of these partial version references in production.

In closing, I hope that I’ve convinced you just how important it is to use a fully qualified references to scripts on the Google CDN. Go forth and enjoy the best caching possible!