In times where web sites behave more and more like web applications, with a constantly growing set of features, most likely developed with JavaScript, it is still of crucial importance that the web site responds to user actions as fast as possible. A couple of months ago, Yahoo! published a set of Exceptional Performance Rules, which - if you adhere to them - can improve the performance of your site significantly. So I had a look how these rules can help me to improve the stats for my own site, www.klauskomenda.com.
Analyzing the present state
Equipped with Firebug and Yahoo!’s YSlow, an extension for Firebug, I first had a look at the current performance measures of this site. Firebug reported the following (under the “Net” tab, select “All”):
| Area | # requests | Datasize (in kB) |
|---|---|---|
| Overall: 29 requests, 194kB | ||
| HTML | 2 | 31 |
| CSS | 5 | 27 |
| JavaScript | 12 | 84 |
| Images | 10 | 52 |
YSlow measures a web site’s performance against the abovementioned Exceptional Performance Rules. It grades the performance for every category and finally comes up with an overall performance score for the whole website. The scores for this site were as follows:
| # | Rule | Score | Reason |
|---|---|---|---|
| Overall Score: F (45) | |||
| 1 | Make fewer HTTP requests | C | I had 13 external JS files, 5 external CSS files and 7 CSS background images |
| 2 | Use a CDN | F | Not using a CDN (Content Distribution Network), cause it is just not worth it in my case |
| 3 | Add an Expires header | F | No expires header set for CSS, JS and images |
| 4 | Gzip components | F | No gzipping of HTML, CSS, JS files |
| 5 | Put CSS at the top | F | CSS files were linked with the @import rule, which apparently made them finally load outside of the head |
| 6 | Put JS at the bottom | A | All JS gets loaded at the very bottom of the page, close to the closing body tag. Why? |
| 7 | Avoid CSS expressions | A | No CSS expressions used |
| 8 | Make JS and CSS external | n/a | Not necessary on this website |
| 9 | Reduce DNS lookups | A | Sources only provided by two sources (max.) |
| 10 | Minify JS | A | geolinkr.js file not minified |
| 11 | Avoid redirects | A | No redirects implemented |
| 12 | Remove duplicate scripts | A | No JavaScript file gets included twice in one page |
| 13 | Configure ETags | F | unconfigured on the Apache server |
As stated by Jeff Attwood, Yahoo!’s Problems Are Not Your Problems, you need to be careful when following the guidelines for improving your YSlow scores. Some of them are only useful if you run a website with millions of unique visitors per day. If used wrongly, they can even slow down your site. So I took those results, tried to applied as much common sense to them and then tried to improve them using methods described in the following section.
Taking action
1. Reducing HTTP Requests
Even from a not-so-much technical and geeky perspective, this makes sense. If the browser needs to communicate with the server less often, this will make the web site load fast (”less talk, more action”). For me, I have chose two measures to achieve this:
a. through Minification
To reduce the number of HTTP requests for JS and CSS files, I decided to “minify” them into one file each. This not only reduces the number of HTTP requests, but also shrinks the filesize down by removing unnecessary whitespace, comments etc. The minified JavaScript file gets included as close as possible to the closing body tag, the CSS file gets called as soon as possible in the document.
b. by creating CSS Sprites
Of course I see the point in generating CSS Sprites which then contains all (or at least) many of the background-images you use on the site and then position them using CSS properties. I can certainly see the advantages of using a tool like CSS Sprite Generator (done by my co-workers Ed and Stuart) but I decided no to because of the following reasons:
- YSlow and Firebug are telling me that I am using 7 background images. I think that is not too bad.
- My background images have mixed formats (JPG, GIF and PNG). Now I could make all of them PNGs and create a CSS Sprite. But I think it does not necessarily make sense, cause you use JPGs for certain images (pictures) and GIFs for images with greater areas of solid colours (e.g. comic illustrations). So mixing all of them into one does not make much sense to me.
- Most likely, I am doing some little design changes here and there and I certainly don’t want to recreate my CSS Sprite every time.
So I dropped that measure from my list, even though it might mean my score will not increase that much.
2. Adding an Expires Header
To ensure that elements are cacheable I configured Apache in a way that it sets an Expires header for 3 days in the future. This means that before those 3 days are over, the browser will always use the cached version of the image/CSS/JS/HTML file (if there is one). The requirement for this to work is that the mod_expires module is installed on your Apache webserver. You can check for that by doing a phpinfo(). If that is not the case and you don’t have access to the httpd.conf file, you won’t be able to set the Expires Header through Apache. If you do have access to the httpd.conf file, add the following line to the LoadModule section and restart Apache.
LoadModule expires_module modules/mod_expires.so
Now that the module is available you can add the following lines to your .htaccess file:
<IfModule mod_expires.c>
# enable expirations
ExpiresActive On
# sets the Expires date 3 days out from the time of the request
ExpiresDefault "access plus 3 days"
</IfModule>
Yahoo! recommends to set the Expires header to +10 years after the time of the request. This might be fine for Yahoo!, but this only seems to be wise if you are dead-serious that this file will never ever gonna change. Cause if you do change it on the server, it won’t force any change on the client side…the user agent will not even bother looking for a new version of that file if it is already in the cache. So unless you rename the file, it will never get picked up by the browser until 2017…I mean, unless you clear the cache, of course.
3. Turning on Gzipping
This can be done using the mod_deflate module, which got introduced in Apache with version 2.0. Again, check if that module is available using phpinfo(). The following line in http.conf will load the module:
LoadModule deflate_module modules/mod_deflate.so
The following additions to your .htaccess file will gzip HTML, CSS and JavaScript files:
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/x-javascript
</IfModule>
4. Putting CSS at the top of the document
Due to the fact that I used one basic stylesheet and, from within that one, imported the other stylesheets using the @import rule, for some reason, YSlow tells me that those stylesheet are not located in the HEAD section anymore. Because of the fact that the CSS is now minified, there is now only one single request to the CSS file using:
<link rel="stylesheet" href="[path to minified CSS file]" type="text/css" />
5. Configuring ETags…maybe not
As this website does not receive high traffic - and I therefore do not run a serverfarm - and based on Jeff Attwoods article, I decided not to configure ETags at all on the server side. If, for some reason, you would like to do it, you are free to do so by making another addition to your .htaccess file. The following turns off ETags altogether:
FileETag none
Actions taken, measuring again…
After incorporating these changes, I measured the performance again with the same tools mentioned above. The results are:
| Area | # requests | Datasize (in kB) |
|---|---|---|
| Overall: 18 requests, 165kB | ||
| HTML | 1 | 31 |
| CSS | 1 | 5 |
| JavaScript | 6 | 77 |
| Images | 10 | 52 |
Here are the noteworthy changes that occured in the YSlow scores including comments as to which scores/rules got affected:
| # | Rule | Score (after) | Score (before) | Reason/Comments |
|---|---|---|---|---|
| Overall Score: B (82) | ||||
| 1 | Make fewer HTTP requests | B | C | Still 6 JS files and 7 background images |
| 3 | Add an Expires header | B | F | Expires header now set, except for external Yahoo! Maps API JavaScript |
| 4 | Gzip components | A | F | Now, all components are gzipped |
| 5 | Put CSS at the top | A | F | CSS gets now included using link within the HEAD |
| 10 | Minify JS | A | A | All JavaScript files get minified |
| 13 | Configure ETags | F | F | Left unconfigured |
Conclusion
By following the Exceptional Performance Rules (well, rather treating them as guidelines and using them based on Web Development common sense applied to your site/project) I was able to improve the performance of this site as follows:
| Area | After | Before | Change |
|---|---|---|---|
| HTTP Requests | 18 | 29 | -39% |
| Page size (in kB) | 165 | 194 | -15% |
| YSlow score | B (82) | F (45) | +37 |
As you can see, the overall performance scores improved quite significantly. They could even be improved by further reducing the number of HTTP requests (using CSS Sprites) or trying to further minify CSS and JavaScript files. Generally, I feel that the Exceptional Performance Rules are excellent guidelines if you want to improve the performance of any particular site. As stated above, before blindly trying to follow these rules, one needs to verify how valid each of these rules is for the site he or she is working on. The rules were made with websites in mind who serve several million visitors a day - if you own one of those sites, then certainly follow these rules. If not (which is more likely), try to find out which of these rules really make sense in your case.
Further resources
- Simone Chiaretta was able to find out the weight applied to each YSlow rule in Dissecting YSlow
- Jeff Atwood tells you why Yahoo’s Problems Are Not Your Problems
- Get YSlow for Firebug
- Yahoo!’s Exceptional Performance Team
- Douglas Crockford’s JSMin
- YUI Compressor, another JavaScript minifier
- CSS Tidy, an opensource CSS parser and optimiser
- Website Performance by Ed and Stuart
Jeff’s advice about ETags is, in my opinion, poorly expressed with double or triple negatives confusing the issue. He says “So unless you run a server farm, you should ignore this guidance.” What he _means_ is “Klaus, you only have a single server: use ETags.” In fact, they really _only_ work (on Apache and IIS) in your specific circumstance.
ETags are a problem at Yahoo! because each page is served from a multitude of servers. Because of the way an ETag is generated, it contains a machine-specific identifier (the inode of the file on the disk for Apache, or the configuration change number for IIS); the ETag for resource X on server 1 is different from the tag for that same resource on server 2. This equals massive fail.
Your case is different: you only have one server, and the ETag for a specific resource will always remain the same. If you turn on ETags, therefore, you’ll trade in at least a few heavy `200 OK` responses for lightweight `304 Not Modified` responses. You’ll be happier, your server will be happier, and life in general will be better.
Mike: thanks very much for clarification, I really got confused by that whole ETags issue…
PS: That’s why I love working for Yahoo!: brilliant and bright people (who even visit my blog)