Skip to content

March 22, 2011

Combine and cache multiple CSS files for performance

This is a simple way to get a performance boost if you’re using PHP + Apache. I am using it to boost performance on GoToQuiz. If you are not using Apache, you can still use the PHP portion of this performance enhancement.

Some quick background: maintaining your site’s CSS in multiple files is frequently useful and sometimes necessary. But including multiple CSS files results in a performance hit when your pages load, because each one requires a separate request from the user’s browser to your server. Each request has associated overhead, which can be minimized if you send all CSS in one request.

This is how you can combine your CSS files with PHP and cache them, relying on a clever bit of .htaccess modification:

First, the PHP portion.  This bit of code expects an HTTP GET parameter called “files” which should hold a hyphen-delimited set of CSS file names.  A sample value would be: “main.css-menu.css-footer.css”. The comments explain what’s going on:

<?php
$filename = $_GET['files']; 
// validate that $filename is set, contains only legal characters  
// and does not contain multiple dots (potential sign of trouble) 
if (!$filename || 
        !preg_match('/^([\.\-\_a-z0-9]*)$/i', $filename) || 
        preg_match('/([\.]{2,})/', $filename))     
    exit(); 
 
// we're sending CSS back to the browser 
header('Content-Type: text/css'); 
 
$files = explode('-', $filename, 15); 
 
// we're also writing CSS to a subdirectory "cache" 
// the filename will be the hyphen-delimited value  
// of $filename 
$cachefile = @fopen('cache/'. $filename, 'w'); 
 
// loop through, read each file, and stitch them together
foreach ($files AS $file) {
    $fcontents = null;     
    if ($cssfile = fopen($file, 'r')) {         
        $fcontents = fread($cssfile, filesize($file)) ."\n";         
        fclose($cssfile);     
    }
 
    // if we read something, write it and send it to browser     
    if ($fcontents) {
        fwrite($cachefile, $fcontents, strlen($fcontents));
        echo $fcontents;     
    }     
} 
 
// all done 
fclose($cachefile);
?>

Save this file as csscacher.php in the same directory as your CSS files (let’s assume that directory is “/css”). A request to /css/csscacher.php?files=main.css-menu.css-footer.css would read those three files, writing their contents (separated by a line-feed character) to the browser as well as a file named /css/cache/main.css-menu.css-footer.css.

Now for the Apache part. Add the following to your .htaccess file:

RewriteCond %{REQUEST_URI} ^/css/cache
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^css/cache/(.*)$ /css/csscacher.php?files=$1 [L]

What this does: all requests for files under the /css/cache directory are tested for existence. If the file does not exist, the rewrite rule is applied. The rewrite rule sends the request to our csscacher.php, with the requested filename passed as the value to the files param. When the first request comes in for /css/cache/main.css-menu.css-footer.css, it will not exist, therefore csscacher.php will be invoked, which will create the file. For all subsequent requests the file will exist, and Apache will serve it speedily as a static file.

To take advantage of this, change your pages from including this:

<link type="text/css" rel="stylesheet" href="/css/main.css">
<link type="text/css" rel="stylesheet" href="/css/menu.css">
<link type="text/css" rel="stylesheet" href="/css/footer.css">

To this:

<link type="text/css" rel="stylesheet" href="/css/cache/main.css-menu.css-footer.css">

Your multiple CSS files have been combined and cached as one. Your csscacher.php will pick up any valid combination of CSS files automatically and cache them. To clear the cache, just delete all files in the cache subdirectory. They’ll then be recreated from the latest source CSS files. Now you will have improved your site’s loading performance. Combining multiple CSS files into one will earn you a better score in performance tools such as YSlow!, and utilizing the .htaccess directive will spare your server from executing a PHP script upon every request.

Read more from PHP

Share your thoughts, post a comment.

(required)
(required)

Note: HTML is allowed. Your email address will never be published.

Subscribe to comments