See comments for important information about HTTP caching issues with URLs containing query string parameters.
When including CSS and JavaScript resources in your pages you should version file paths and update these version numbers every time the files change. This is necessary as a visitor's browsers may, depending on settings, continue to cache files even after a change. This can result in a mismatch between your HTML and these external resources which may cause rendering or functionality problems. One will likely encounter similar caching issues with images.
So if we work on the basis that we should version these resources and that changing version numbers forces the browser to reload those resources then we can actually gain large performance improvements by explicitly instructing the browser to cache them for an extended period of time (say 10 years) thereby limiting the number of times the browser goes back to check for fresh copies.
This can be achieved fairly easily within the virtual host settings of your Apache conf file. Simply add the following directives within the relevant virtual hosts section substituting /assets/ with the file path corresponding to the location where your CSS, JavaScript and images are stored.
<Location /assets/>
ExpiresActive On
ExpiresDefault "access plus 10 years"
</Location>
If you have the mod_deflate module installed then you can gain an additional performance benefit by gzipping CSS and JavaScript resources to reduce the amount transmitted down the wire.
<Location /assets/>
ExpiresActive On
ExpiresDefault "access plus 10 years"
SetOutputFilter DEFLATE
</Location>
These directives can also be added to an appropriately placed .htaccess file but as Stuart explains there are performance implications with this method so it should only be chosen if you don't have access to your Apache conf files (perhaps because you're using shared hosting).
Applying both these settings covers off a couple of recommendations made by Yahoo!'s YSlow performance testing tool so not only will you end up with a faster site but you'll also improve your YSlow rating by a grade or two.
Of course manually versioning files is a pain, it's repetitive and as Stuart is always reminding me manually repetitive tasks should be eliminated wherever possible. In the past I wrote a script which automatically merges CSS or JavaScript files and versions the combined output. Here's some simpler code which versions individual files based their last modified date - every time the file is updated it's version number updates accordingly.
<?php
class Version {
private static $aLookup = array();
public static function Get($sFilename) {
if (!array_key_exists($sFilename, self::$aLookup)) {
$sRealPath = realpath($_SERVER['DOCUMENT_ROOT'] . "/$sFilename");
if (file_exists($sRealPath) && ($iTimestamp = filemtime($sRealPath))) {
self::$aLookup[$sFilename] = $iTimestamp;
$sFilename .= "?v=$iTimestamp";
}
} else {
$sFilename .= '?v='.self::$aLookup[$sFilename];
}
return $sFilename;
}
public static function GetLink($sFilename) {
return '<link rel="stylesheet" type="text/css" href="'.self::Get($sFilename).'">';
}
public static function GetScript($sFilename) {
return '<script type="text/javascript" src="'.self::Get($sFilename).'"></script>';
}
public static function GetImage($sFilename, $iWidth, $iHeight, $sAlt = '') {
return sprintf('<img src="%s" width="%d" height="%d" alt="%s">', self::Get($sFilename), $iWidth, $iHeight, $sAlt);
}
}
?>
Download plain text version (Updated version to fix HTTP caching issues)
Using it is pretty simple - wrap the required function around the file path you'd normally have supplied to link, script or img tags.
<link rel="stylesheet" type="text/css" href="<?php echo Version::Get('/css/dark.css'); ?>">
In fact it's even easier than that - I've also provided some wrapper functions which make outputting link, script and img tags a little bit easier. The example above could be written more simply as follows:
<?php echo Version::GetLink('/css/dark.css'); ?>