Rafał Wrzeszcz - Wrzasq.pl

Serving static content (CSS and JavaScript) with GZip

Saturday, 23 July 2011, 18:29

The theory

When you publish your project in production environment you usualy have some build tasks to be done for each release. For web projects it's good to take care about static contents there - mainly images, CSS and JavaScript. If your project is going to work under heavy load, one of the optimization field will be response size and time. While you will mainly look into your engine/application to shortnen it's response handling time, sometimes it may not be the case. The other point at which you should look is serving static content. Even when those files are stored on separated static server usualy one page load requires loading planty of additional static files. One of the good options is to compress them - smaller size means both smaller bandwidth and shorter loading time for clients.

This description should apply to all types of files, however I will especialy focus on CSS and JavaScript. These are usualy critical parts of web projects deployment process and they are text-based contents, so they can be compressed with a very good ratio. In terms of HTTP protocol compressing usualy means just gzipping - the only other widely implemented and supported compression algorithm is deflate, but gzip is basicly just a deflate with a checksum checking, so i prefer it.

Building

Before you will step to serving compressed content, you should also take a look for code minifying tools, like YUI Compressor (it handles both JavaScript and CSS, there are more specific tools, that are better for particular format, like Google Closure Compiler). Minifying tools should be your build step between development and production environments - they reduce source code size by reconstruct it dropping all unrequired characters and refactoring the code to use as short identifiers and language constructions as possible. For example YUI Compressor will replace this:

function foo(bar) {
    // comments will be dropped
    var baz = bar;
    return function(qux) {
        return (bar) ? baz : qux;
    };
}

With this:

function foo(a){var b=a;return function(c){return(a)?b:c}};

Before minifying you should produce just one single CSS and one JavaScript files based on all your development files (however this is not required for this solution to work).

I will try to describe the build process itself better other time.

Serving

Before playing with Apache and .htaccess, the last preparation step is to have gzip-ped version of static files - they should be placed in same directory and named just like original files, but have .gz suffix. Since gzip command itself replaces source file, probably the most handy way to achive that is this:

cat file | gzip > file.gz

This notation allows you to put it into for example Makefile and parameterize:

compress: public/javascript/prototype.js.gz public/styles/styles.css.gz public/sitemap-static.xml.gz

%.gz: %
    cat $^ | gzip > $@

You probably wonder why you need both files - uncompressed and compressed. Here comes the trick - all incommeng references should point to uncompressed file. Some browsers may not support content compression. You can of course try to detect that feature and serve separated version of web pages - but why bother, you can just handle that at one place for all.

Configuring Apache

Ok, time to play with server configuration. In fact the snippet below can in monst cases be just copy-pasted, so I before your eyes reach it, I will describe some problems connected to it and tell why things are done the way they are done.

First line - AddEncoding: this simply makes Apache2 to tell clients that content is gzipped usig Content-Encoding header.

First rewrite condition - RewriteCond %{HTTP:Accept-Encoding} gzip checks if client supports gzip encoding.

Second rewrite condition - RewriteCond %{REQUEST_FILENAME}.gz -f checks if we have gzipped version of file.

The rewrite rule simply redirects server internaly to gzipped file (leaving all query parameters).

The last part of configuration contains additional type headers for particular file extensions (you may meet the case, where your files have non-standard extensions and you will have to specify ForceType per-file). This is a very important part! If you will tell a browser to download prototype.js it will expect it to has JavaScript type. However, when Apache will handle rewriting rules, it will handle prototype.js.gz file in fact, so it will send it as (usualy) application/x-gzip. Browser may know how to handle gzipped content, but having mismatched Content-Type headers is a completly different story. The same applies to CSS files.

AddEncoding gzip .gz

RewriteEngine On

RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{REQUEST_FILENAME}.gz -f
RewriteRule ^(.*)$ $1.gz [QSA,L]

<Files *.css.gz>
ForceType text/css
</Files>

<Files *.js.gz>
ForceType application/javascript
</Files>

You may notice application/javascript MIME type for JavaScript files. This is the recommended standard and text/javascript, while still allowed, is obsolete. However you may want to switch to it for older browsers.

On-the-fly

Other way is to compress served files dynamicly (using for example mod_gzip). This may be good for dynamic content, the essence of having static content is performance - handling compression on-the-fly requires additional CPU operations and internal Apache calls to the module (at least). This is definetly not what you expect to happen when you serve static contents. So having static files compressed just once speeds entire serving process alot. This is what are we essentialy looking for, right?

Tags: Apache2, GZip, CSS, JavaScript, Server, Optimization, Production, .htaccess