As a follow-up to my post on compressing .php, .css and .js files without mod_gzip or mod_deflate, I’m documenting the changes I made to the .htaccess file on ardamis.com in order to speed up page load times for returning visitors and satisfy the Leverage browser caching recommendation of Google’s Page Speed Firefox/Firebug Add-on.
A great explanation of why browser caching helps the web deliver a better user experience is at betterexplained.com.
Two authoritative articles on the subject are Google’s Performance Best Practices | Optimize caching and Yahoo’s Best Practices for Speeding Up Your Web Site | Add an Expires or a Cache-Control Header.
I’d like to point out that in researching browser cashing, I came across a lot of information that contradicted the rather clear instructions from Google:
It is important to specify one of
Cache-Control max-age, and one of
ETag, for all cacheable resources. It is redundant to specify both
Cache-Control: max-age, or to specify both
I’m not sure that this recommendation is entirely correct, as the W3C states that Expires and Cache-Control max-age are used in different situations, with Cache-Control max-age overriding Expires in the event of conflicts.
If a response includes both an Expires header and a max-age directive, the max-age directive overrides the Expires header, even if the Expires header is more restrictive. This rule allows an origin server to provide, for a given response, a longer expiration time to an HTTP/1.1 (or later) cache than to an HTTP/1.0 cache.
It would seem that Cache-Control is the preferred method of controlling browser caching going forward.
HTTP 1.1 clients will honour “Cache-Control” (which is easier to use and much more flexible).
HTTP 1.0 clients will ignore “Cache-Control” but honour “Expires”. With “Expires” you get thus at least a bit control for these old clients.
In any event, Page Speed won’t protest if you do end up sending both Expires and Cache-Control max-age, or if you remove both Last-Modified and ETag, but I was able to get the best results with just setting Cache-Control max-age and removing the ETag.
Setting the headers in .htaccess
On Apache, configuring the proper headers can be done in the .htaccess file, using the
Header directive. The
Header directive requires the
mod_headers module to be enabled.
Add the following code to your .htaccess file to set your Cache-Control and Expires headers, adjusting the date to be one year from today.
# Set Cache-Control and Expires headers <filesMatch "\.(ico|pdf|flv|jpg|jpeg|png|gif|swf|mp3|mp4)$"> Header set Cache-Control "max-age=2592000, private" Header set Expires "Sun, 17 July 2011 20:00:00 GMT" </filesMatch> <filesMatch "\.(css|css.gz)$"> Header set Cache-Control "max-age=604800, private" </filesMatch> <filesMatch "\.(js|js.gz)$"> Header set Cache-Control "max-age=604800, private" </filesMatch> <filesMatch "\.(xml|txt)$"> Header set Cache-Control "max-age=216000, private, must-revalidate" </filesMatch> <filesMatch "\.(html|htm)$"> Header set Cache-Control "max-age=7200, private, must-revalidate" </filesMatch>
Removing ETags in .htaccess
Most sources recommend simply removing ETags if they are not required.
Entity tags (ETags) are a mechanism that web servers and browsers use to determine whether the component in the browser’s cache matches the one on the origin server.
If you’re not taking advantage of the flexible validation model that ETags provide, it’s better to just remove the ETag altogether.
Add the following code to your .htaccess file to remove ETag headers.
# Turn off ETags FileETag None Header unset ETag
Set Expires headers with ExpiresByType (optional)
If your host has the
mod_expires module enabled, you can specify Expires headers by file type. Godaddy does not have this module enabled.
Removing the Last-Modified header in .htaccess (optional)
I’m following Google’s instructions and not removing the Last-Modified header, but if you wanted to do so, you could use:
# Remove Last-Modified header Header unset Last-Modified
Busting the cache when files change
What happens when you change files and need to force browsers to load the new files? Christian Johansen offers two methods in his post on Using a far future expires header.