During a meeting at work today, we looked at my web site (as an example) to see if there are ways the performance could be improved. I was surprised to see how many things could be done to make it a big faster.
One of the most trivial changes involved adding an Expires: header which specifies a time far enough in the future so that clients (browsers) won't try to re-fetch images that haven't changed.
Since I'm running Apache 1.3, I dug up the old documentation on mod_expires and made a few simple additions to httpd.conf:
ExpiresActive On ExpiresByType image/gif A2592000 ExpiresByType image/png A2592000 ExpiresByType image/jpg A2592000 ExpiresByType image/jpeg A2592000
That asks Apache to set an Expires header that's roughly one month from the moment at which the browser requested the image. Here's a quick test to show that it worked:
root@litterbox:~/w/i# telnet localhost 80 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. HEAD /i/mini-softie.jpg HTTP/1.0 Host: jeremy.zawodny.com ... HTTP/1.1 200 OK Date: Mon, 23 Jul 2007 22:03:35 GMT Server: Apache/1.3.34 (Debian) mod_gzip/1.3.26.1a PHP/4.4.4-8 Cache-Control: max-age=2592000 Expires: Wed, 22 Aug 2007 22:03:35 GMT Last-Modified: Tue, 29 May 2007 15:51:04 GMT ETag: "95c67-5995-465c4be8" Accept-Ranges: bytes Content-Length: 22933 Connection: close Content-Type: image/jpeg
Excellent. August 22nd is about a month from now.
In theory I could go quite a bit farther into the future since I almost never replace images on my site.
Posted by jzawodn at July 23, 2007 02:14 PM
Bah.
And stop typing my GET and HEAD requests by hand?!?! Next thing you know someone will suggest that I ditch this fountain pen and stone tablets too...
commandline function:
gethead () {
lynx -dump -head $@
}
web interface:
I think most modern browsers these days respect the Cache-Control header as well. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9)
I usually use something like:
Cache-Control: public, max-age=SOME_NUMBER_OF_SECONDS
You can do much better than that (basically you can make it work when you do replace the images too). :-)
I do it slightly differently, but Cal wrote a good article of the general concept here:
http://www.thinkvitamin.com/features/webapps/serving-javascript-fast
- ask
Oh, as an example of it in action:
If you look at the html source at http://www.yellowbot.com/ you will see for example
http://css.st.ypbot.net/prod/css/master.v2559.css
if you fetch that file it'll expire in a long time. If you fetch
http://css.st.ypbot.net/prod/css/master.css
then you don't get any explicit expiration headers and your browser is expected to Do The Right Thing.
In our code we have some helpers to put in the appropriate "version number" (from subversion revisions).
- ask
I hadn't seen that article by Cal. Thanks for the pointer.
Jeremy, I'd love to see more tips like this from you. I can use them on some of my websites.
Also, where in the file would one put this? Does it matter?
Wow, great timing. I just installed YSlow for Firebug and was wondering how to do this. However, many of my static images are on S3 - how can I get S3 to do the same?
@Paul
From:
http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTObjectPUT.html
and
http://developer.amazonwebservices.com/connect/thread.jspa?messageID=59615
It looks like if you use the REST API to upload your objects, you can specify Cache-Control and Expires headers on the PUT that get sent back verbatim for any GET's. This probably means Cache-Control would be the way to go for S3, since a static Expires time doesn't accomplish the "set the Expires time to access plus n seconds" as Jeremy's Apache config snippet does.
(Disclaimer: I have not tried this so I may be wrong)
oops, that second url should be:
http://developer.amazonwebservices.com/connect/thread.jspa?messageID=59615
This tool could/should put more weight on stuff that gets served within the domain the page comes from. I got what I think is an artificially low score on stuff I don't control (I can't gzip stuff at Amazon or Google, frinstance).
Think the performance engineering group would concur?
I used to run a site that had a very large number of small static images for icons and such, and a lot of them would be loaded on nearly every page. My work around for this was to set the Expires header on all of them to never, and then I would always generate URLs for them of the form http://site.com/static-images/image.png?. Any time I actually changed the images, I would increment the serial, which would cause the URL to change, and people would retrieve them again.
Jeremy,
Thanks for pointing out this good tip. I used it to help speed up (considerably) the loading of icon graphics in RSS2.com. Now I'm going through the other tips from the YSlow application to see what else I can do to optimize client-side load times.
Thanks again.
Thanks Jeremy, great material. Here's what I used for my high-traffic web site:
# Cache static content for 6 months
ExpiresActive On
ExpiresByType image/png A15552000
ExpiresByType text/javascript A15552000
ExpiresByType text/css A15552000
Great sharing man, it works excellent. I applied it to my site and it improved the speed upto 25%
Sorry for the noob question but where do you put the code in a self hosted Wordpress blog?
@ZXT -- for self hosted wordpress with a hosting company see if you have access to the .htaccess file. It is a config file for apache in the root of the web directory.
Hi, anytime I try to add a line about expiration or compression in my htaccess file, I get an Internet Server Error and need to remove this line, do you know why? thank you,
Eric
Simple implementation of static header on web.config.
http://jeeshenlee.wordpress.com/2010/07/31/how-to-add-expires-headers-in-asp-net/