After a bit of testing with Yslow, I decided to speed up browser loading of this site’s web pages. Unfortunately, the methods needed vary depending upon the configuration of your web server and the type of content you have. Fortunately, figuring this all out didn't take much time at all, particularly since there’s a lot of guidance out there. What follows is what I finally set upon to speed up serving this site. Since the pages on this site reuse many resources, the primary speed-up was from adding cache-control and expires headers.
My main optimization was to add an expires or a cache-control header. From the Yslow documentation:
There are two things in this rule:
- For static components: implement "Never expire" policy by setting far future Expires header
- For dynamic components: use an appropriate Cache-Control header to help the browser with conditional requests
Web page designs are getting richer and richer, which means more scripts, stylesheets, images, and Flash in the page. A first-time visitor to your page may have to make several HTTP requests, but by using the Expires header you make those components cacheable. This avoids unnecessary HTTP requests on subsequent page views. Expires headers are most often used with images, but they should be used on all components including scripts, stylesheets, and Flash components.
For the static pages, this was easy enough, with just a few lines in the .htaccess file:
<FilesMatch "\.(ico|pdf|flv|jpg|jpeg|png|gif|js|css|swf)$">Header set Cache-Control "max-age=290304000, public"Header set Expires "Thu, 15 Apr 2018 20:00:00 GMT"</FilesMatch>The issue is a bit trickier with dynamic pages. For static resources (e.g., images, script files, and html pages), Etags and Head requests will be handled by the server, but that (and cache information) is best handled by the page itself for dynamic pages. Fortunately, Alexandre Alapetite has developed a very simple method for handling all that, and gzip compression too: HTTP conditional requests in PHP.
Setting far future Expires headers causes one very big problem when you attempt to update those files:
Keep in mind, if you use a far future Expires header you have to change the component's filename whenever the component changes. At Yahoo! we often make this step part of the build process: a version number is embedded in the component's filename, for example, yahoo_2.0.6.js.
I didn’t want to change a version number each time I edited the file. Kevin Hale (and others) recommended automatic versioning of css and js files. In this method, you use php to add a date stamp to the file name request. The browser will then, whenever the file is updated, treat the request as if it were a new file, and request a copy from the web server. On the server side, a rewrite rule in the .htaccess file strips out the date stamp, so the correct file is served. I added the following lines to the .htaccess file:
RewriteEngine on RewriteBase / RewriteRule ^(.*)\.[0-9]+\.(ico|pdf|flv|jpg|jpeg|png|gif|js|css|swf|php)$ $1.$2 [L]And here is the php code I developed for adding a timestamp to the requested file:
<?phpfunction autoVer($url){$name = explode('.',$url); #Split apart the url at each period (some file names may include more than one period)$lastext = array_pop($name); #pop off the array the text after the last period, as that is the file extensionarray_push($name,filemtime($_SERVER['DOCUMENT_ROOT'].$url),$lastext); #push on the array first the file time stamp, then the file extensionecho implode('.',$name); #reassemble everything, putting periods between the pieces}?>Then, whenever you want to include a file with automatic versioning, just require_once the autoVer function, and, instead of using the file name in the web page, you use the following php code: "<?php autoVer('/path/to/file.ext'); ?>". For example: <link rel="stylesheet" type="text/css" href="<?php autoVer('/path/to/file.ext'); ?>" />. When that php code is executed, the link will be changed to something like <link rel="stylesheet" type="text/css" href="/path/to/file.1981723948.css" /> (the numbers will change, depending upon the last save date of file.css). The browser will request /path/to/file.1981723948.css, and the server will, because of the rewrite rule, translate that request to /path/to/file.css and send back the correct file. When file.css is updated, the php function will yield a different filename (e.g., /path/to/file.2154826964.css), so the browser will ignore the previously cached file, and request a new copy from the server.
Entity tags (Etags) are one way to identify files on the server and detect changes. However, the standard setup causes problems if your files may be served from more than one source (i.e., your web site hosting company uses virtual servers). That is easily fixed by adding the following line to your .htaccess file: FileETag MTime Size. Etags for dynamic pages need to be generated by the page itself—a function found in Alexandre Alapetite’s HTTP conditional requests in PHP.
Most browsers can handle files served in a compressed form. For files that are not already compressed, this can be a major speedup. Unfortunately, the server I use apparently does not have this ability turned on. If it were, I’d add the following to the .htaccess file:
<IfModule mod_gzip.c>mod_gzip_on Yesmod_gzip_dechunk Yesmod_gzip_item_include file \.(html?|txt|css|js|php|pl|jpg|png|gif|xml)$mod_gzip_item_include handler ^cgi-script$mod_gzip_item_include mime ^text/.*mod_gzip_item_include mime ^application/x-javascript.*mod_gzip_item_exclude mime ^image/.*mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.*</IfModule>For php files, you can use php built-in functions to handle compressing the file. I combined it with cache management by using Alexandre Alapetite’s HTTP conditional requests in PHP function.
Easy enough to do. For XHTML pages, the scripts are not allowed after the </body> tag.
This software is licensed under the CC-GNU GPL. 