Performance Tuning Wordpress: Far-Future Headers and Cache Busting

Recently, for the first time, I dove fairly deeply into Wordpress for a client site. While working on optimizing the front end, I devised a simple solution for caching assets. Ideally you want browsers to cache CSS, JavaScript, images and other assets that rarely change to reduce the number of HTTP requests needed to load a page.

A number of caching plugins exist for Wordpress, but they all seemed a little more complex than I needed. Plus I like to rely as little on 3rd party code as I can. So here’s a simple solution using a single php function and .htaccess file.

First, Enable Caching

We can instruct browsers to cache our CSS, JavaScript and images assets. Once a browser caches these files, when it encounters them again, it will use its local cached versions rather than re-requesting them, significantly increasing page load times.

To set up caching in Apache you just add a few mod_expires directives to your .htaccess file. First, if you don’t already have one, create a text file, name it .htaccess (notice the leading dot), and save it in the root of your Wordpress installation (make sure you save this in the root of your Wordpress installation, not your theme folder). Add the following lines.

.htaccess

<IfModule mod_expires.c>

ExpiresActive on
ExpiresByType application/javascript "access plus 6 months"
ExpiresByType image/jpg "access plus 6 months"
ExpiresByType image/jpeg "access plus 6 months"
ExpiresByType image/gif "access plus 6 months"
ExpiresByType image/png "access plus 6 months"
ExpiresByType text/css "access plus 6 months"

</IfModule>

These “far future” expires directives tell browsers to cache CSS, JavaScript and image files for six months. I’ve chosen six months, but you can decide what is best for you. Best practice is usually somewhere between one month up to one year. Google has a nice overview of caching for optimization.

Second, Bust the Cache

But what happens when you do update your CSS or JavaScript? The browser is going to keep using its cached version for six months. How do you let it know to ignore its cache and get the updated version? The best way to do this is to rename the file - normally by embedding some sort of timestamp or version number into the file name. You could do this manually, but then you would have to also update any and all references in your HTML. Luckily we can do this automatically with a simple php function. Add the following to the functions.php file in your Wordpress theme.

functions.php

<?php

function cache_buster_url($uri) {

	//Get base url of WP installation
	$base_url = get_bloginfo('url');

	//Get local path to the root of the WP installation
	$installation_root = $_SERVER['DOCUMENT_ROOT'];

	//Get path (from root) to file
	$path_info = str_replace($base_url, '', $uri);

	//If path has no dot (no file extension), return unchanged
	if(strpos($path_info, ".") === false) { return $path_info; }

	//Get full local path to file
	$local_path = $installation_root . $path_info;

	//Generate numeric timestamp based on modification date
	$cache_buster_date = date("YmdHis", filemtime( $local_path ));

	//Add the timestamp to the file name, right before the file extension
	$url_chunks = explode(".", $path_info);
	array_splice($url_chunks, count($url_chunks) - 1, 0, $cache_buster_date);

	//Return full, rewritten uri
	return $base_url . join(".", $url_chunks);
}

?>

In a nutshell here’s what’s happening. The function…
1. Takes the URL to a file and converts it to a local file path,
2. Checks when the file was last modified and
3. Adds a timestamp right before the file extension.

So, for example, if you pass this URL to the function http://example.com/styles/styles.css, and that file was last changed on Oct 1st 2013 at 10:00 am, the function will return http://example.com/styles/styles.20131001100000.css. Notice the added timestamp. Now each time you update your style sheet (or JavaScript or image or whatever) you’ll get a new, dynamically generated URL, which will force browsers to fetch the updated version.

To use this function just add it to your header.php (and any other template files that reference assets). For example.

Wordpress Template File

<!doctype html>
<html <?php language_attributes();?>>
<head>
	<meta charset="UTF-8">
	<title><?php wp_title();?></title>
	<meta name="viewport" content="initial-scale=1.0, width=device-width" />
	<meta charset="<?php bloginfo('charset');?>" >
	<link rel="stylesheet" href="<?php echo cache_buster_url(get_bloginfo('stylesheet_url')); ?>">
</head>
<body>
</body>
</html>

Notice in line 8, we wrap the call to get_bloginfo('stylesheet_url') in our cache_buster_url function. The stylesheet URL returned by get_bloginfo() gets a timestamp added to it. If the get_bloginfo function looks foreign to you, check out the Wordpress documentation.

Third, Avoid “File Not Found”

Now that we’ve handled dynamically versioning our asset file names, we have to handle the process in the opposite direction. There is no file named http://example.com/styles/styles.20131001100000.css, so when the request comes in to the web server, we’re going to get a 404 file not found error. What we have to do is make sure that requests for these timestamped URLs get translated back to the real, non-timestamped file name. Luckily, if you’re using Apache, this is easy to do with a few lines added to your .htaccess file.

The Apache module mod_rewrite can take an incoming url and rewrite it before passing it on. With a few lines we can take any request for our assets that contains a timestamp and strip the timestamps out leaving the original, “real” file name. Add this to your .htaccess file…

.htaccess

# Set +FollowSymLinks otherwise mod_rewrite may result in a forbidden error
Options +FollowSymLinks

<IfModule mod_rewrite.c>

RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]

# Strip timestamp (cache buster) from filename
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L] 

# Wordpress Pretty URLs
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]

</IfModule>

To understand the full syntax, see the mod_rewrite documentation. What is of interest to us here are lines 11-13. Lines 11 and 12 make sure that there is not a real file or directory (respectively) matching the request. If not, line 13 rewrites any request for a JS, CSS, PNG, JPG or GIF file, stripping out the timestamp. Now http://example.com/styles/styles.20131001100000.css changes back to http://example.com/styles/styles.css.

Conclusion

So this is kind of a long explanation. Let’s quickly recap what’s going on.

  1. We configure our HTTP headers so that they tell clients to cache CSS, JavaScript and image files for 6 months.
  2. We use the php function cache_buster_url() in our theme templates to rewrite CSS, JavaScript and image references, adding a timestamp. That way, when we do update a file, it automatically gets a new name and forces browsers to re-request it instead of using their cached version.
  3. We configure our web server to rewrite incoming requests for assets, stripping out the timestamps.

The result? The first time a browser requests a web page it caches all of our CSS, JavaScript and image files for six months. When we do update a file, the browser will know to re-request it and again cache it for six months. That’s it. You’ll likely see significantly faster page loads for your visitors and less bandwidth usage for your web host.

comments powered by Disqus