Using .htaccess to Redirect to Minified and Pre-Compressed Assets

Minification and compression of assets (JS and CSS) is a common practice across the Web today. It improves performance by reducing the amount of data that has to be transferred over the network, without changing the behavior of those assets. Because those assets are text, the size reductions can be dramatic, especially when we use both techniques together. Using these techniques, though, is not necessarily transparent.

Generally, minified assets are referred to with a .min.* extension to indicate they are different (thus, minified jquery.js becomes jquery.min.js). Because this convention is in the file name, it must also pass down into our references: <script src="jquery.min.js"></script>. To switch between the minified and un-minified versions, we have to actually change the code in the HTML. Source maps are a different, potentially more robust solution, but require support both in the browser and in build to generate those files.

Compressed assets are even harder to manage. Assets can be compressed on the fly using something like mod_deflate or similar. The problem then is that the same compression is done over and over. In addition, some hosts will disable on-the-fly compression at times of heavy load, when that compression might be most beneficial. Pre-compression solves this, and allows the maximum compression to be applied just once. This saves resources on the server, and could result in an even smaller output than on-the-fly compression. The problem with precompiled assets is that they cannot be used transparently. Linking directly to a gzipped JS or CSS file will not have the intended effect; it would look like a file download and would fail. We would need to set the Content-Type and Content-Encoding headers correctly for the browser to recognize that the response is a compressed asset.

Apache provides a flexible interface for rewriting paths with mod_rewrite. We can change paths internally, or externally with HTTP redirects. We can add headers dynamically. We can do rewriting conditionally based on a variety of conditions. Using .htaccess, we can accomplish directing links to the proper minified, pre-compiled versions transparently.

I am by no means an .htaccess expert, so this took me a little while to figure out. I was able to find a StackExchange question that pointed me (significantly) in the right direction. The problem was that I am trying to use it within a CakePHP installation, so it didn't quite work exactly right. With some help from htaccess tester and the docs, I was able to tweak it to get it to work for me. Here is the resulting portion of the .htaccess file.

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /

# Redirect to a minified version, if available
RewriteCond %{REQUEST_FILENAME} (.*)\.(html|css|js|xml|rss|svg)$
RewriteCond %1.min.%2 -f
RewriteRule (.*)\.(html|css|js|xml|rss|svg)$ $1.min.$2 [L]

# Redirect to gzipped version, if available
RewriteCond %{HTTP:Accept-Encoding} .*gzip.*
RewriteCond %{REQUEST_FILENAME}.gz -f
RewriteRule (.*)\.(html|css|js|xml|rss|svg) $1.$2.gz

With this code, we can simply write <script src="jquery.js"></script>, and the link internally will be rewritten to jquery.min.js.gz, with appropriate headers set. What's especially nice about this configuration is that it provides graceful fallback. If the browser doesn't accept gzip-compressed responses, this is detected and a plain version is sent. If a minified or compressed version is not available, it falls back to the next smallest version (no compressed → just minified, no minified → un-minified). This means that to get the un-minified version, we just need to remove or rename the minified version from the server.

The code above covers more than just JS and CSS files. It also allows for redirecting static HTML pages, XML files, RSS feeds, and SVG images, but only if the minified/compressed versions are available. In addition to JS and CSS, I've found quite a bit of savings from minifying and pre-compressing SVG files especially.

For CakePHP, this went into the .htaccess file in the app/webroot directory.


Popular posts from this blog

The Timeline of Errors

Magic Numbers, Semantics, and Compiler Errors

Assuming Debugging