Speed Up Your Web Pages With Browser Caching

John Reeve | May 4th, 2011 | , , , ,

Speed Up Your Web Pages Using the Expires HeaderA fast loading web page is an important factor that heavily weighs into a user’s experience on your web site. And it’s not just your audience taking notice of how quickly a page loads into their browser, Google uses this metric as part of their PageRank algorithm. Web designers and developers who are creating web sites will want to take this into consideration, and usually do by optimizing image file sizes, using semantic HTML and CSS, and controlling the amount of content that appears on any given page to leverage browser caching. However, web designers and developers can decrease their page load times even more by forcing the browser to cache specific types of content via the Expires header. In fact, both Google and Yahoo recommend implementing browser caching by setting the Expires header.

An overview of browser caching using the Expires header

Most web servers, for example, Apache and Nginx, can be configured to include the Expires header in the response for content that doesn’t change very often. When the web browser sees the Expires header in a response, it will not make any future requests for that file until the time allotted has expired or until the browser is forced to request the file again (i.e. a force refresh or a page reload). This is beneficial for web pages containing static images, Javascript and CSS, files that do not change often. By enforcing the browser cache refresh with the Expires header, the browser will pull those static files from it’s cache instead of requesting them from your web server.

Without the Expires header, the web browser will send a request to the web server and receive a 304 status code (not modified) in the response header. At that point it will pull the requested file from it’s own cache. The problem with this approach is that the web browser still has to make the initial request for that file, which costs you an available connection on the web server. When you use the Expires header, the web browser does not make that initial request at all, and instead goes straight to it’s cache to retrieve the file. The web server does less work and the page loads faster. It’s a win-win.

How to implement the Expires header in Apache

Apache is the world’s most popular web server and is most likely what you are using to serve up your web site. Implementing the Expires header using Apache requires the module mod_expires. First, decide what type of content will be cached by the browser, then configure the Apache web server to instruct the browser via the Expires header.

For example, if you wanted to force the web browser to cache all CSS files for one month, add the following to your .htaccess file in the directory containing the CSS files:
ExpiresActive On
ExpiresByType text/css "access plus 1 month"

Note: this code can also be added to the server config, virtual host, or directory, however, these conventions are less accessible. Most web developers should have the ability to create and upload a .htaccess file.

How to implement the Expires header in NGINX

NGINX has already become the fourth most popular web server on the Internet, putting it well ahead of Lighttpd and just behind Microsoft IIS. It’s a very fast web server with plenty of plugins available from the NGINX community. NGINX can also be configured to respond with the Expires header for certain types of content using the HTTPHeadersModule.

Using the same example of caching CSS files for one month, add the following to your nginx.conf file:
location ~* \.css$ {
expires 30d;
}

Note: NGINX also allows the Expires header to be set to “max” to instruct the browser to cache files indefinitely.

Fingerprinting: How to account for when static content changes

Now that we’ve shown how to instruct the web browser to cache static content, what do we do when that static content changes and we want the web browser to re-cache the content? Now that the browser is no longer communicating with the web server to retrieve the cached files, we need a way to tell the browser to request the changed file.

We have to enable dynamic caching by fingerprinting each file so that it has a unique filename. Fingerprinting allows us to set Expires headers for dates way out in the future while still enabling us serve up the new file when it is changed. For this to work, each cached file must have a unique filename. When the contents of the file change, so does the filename, thereby informing the web browser to make a new request to the web server.

To keep track of the unique filenames we maintain a file containing key/value pairs that map the original filename to the unique filename. For example, it maps screen.css to something like screen_b2f6811c5d2b993488d538368fe81c9d.css. We’ll explain more about how to do this below.

Examples of Fingerprinting static content in PHP

Assuming you are working in PHP, here is how you would implement a CSS fingerprinting mechanism. The goal is to create a unique filename for each file, a key/value map, and a way to link in the fingerprinted file.

  1. Fingerprint each of the files
    To obtain a unique filename for each file, we use the md5_file() function. This function returns the md5 hash of a given file. Then we copy the original file to a new file using the returned md5 hash.
    <?
    $dir = 'i/css/';
    $filename = 'screen.css';
    $ufilename = md5_file($dir . $filename);
    copy($dir . $filename, $dir . $ufilename);
    ?>
  2. Create a key/value array to map the filenames
    The next step is to create a PHP array that maps the original filename to the unique filename. This array can be stored in a PHP file that is generated by a script using the code form step 1. This file will also need to be included on any page using the static content, as we’ll see in step 3.
    <?
    $ufilenames = array(
    'screen.css' => 'screen_b2f6811c5d2b993488d538368fe81c9d.css'
    );
    ?>
  3. Reference the static content using the key/value array
    Finally, we add a snippet of PHP code to our web page that will link in the correct static file.
    <? include('css_map.php'); ?>
    <link rel="stylesheet" href="<?= $ufilenames['screen.css'] ?>" type="text/css" />

That’s it!

Now you have the basic beginnings for configuring browser caching for your web site. Although we’ve used CSS fingerprinting in the examples, this methodology can be extended to any type of static file, including Javascript and images. Once implemented, you’ll notice your web site loading faster and faster as you click around. And Google will notice too, and reward your web site with a higher ranking in it’s search results.

Photo credit: tacoekkel

8 Responses to “Speed Up Your Web Pages With Browser Caching”

  1. Prks says:

    Hi,

    I am student and willing to optimize my website.I tried many ways to configure Add Expires Header in Win 7 from apache 2.2.17 But couldn’t go through it.I did following different things…to configure …

    first of all i enable Mob_expires,mod_headers in httpd.conf…and write these things in .htaccess but finally not in my hand…

    # Turn on Expires and set default to 0
    #ExpiresActive On
    #ExpiresDefault A0

    # Set up caching on media files for 1 year (forever?)
    #
    #ExpiresDefault A29030400
    #Header append Cache-Control “public”
    #

    # ExpiresActive

    #on #ExpiresDefault “access plus 1 year”

    #FileETag MTime Size

    ExpiresActive On
    ExpiresByType text/css “access plus 1 month”

    anyway body is here…to help me to get though this .

    thank in advance..

  2. John Reeve says:

    Prks,

    The code in the .htaccess file looks correct. There are a few things I would check. First, check httpd.conf to make sure it has enabled .htaccess files to be parsed. Second, make sure the AllowOverride Indexes option is enabled for this DocumentRoot. And third, use a tool such as Curl to analyze the request for the CSS files to see if the header is indeed being set.

    The .htaccess file should be placed in the same directory as the CSS files, or in a parent directory above them.

    If the above has all been verified and it is still not working, you will need to check the apache error logs to find out exactly what is going on.

  3. Gr8Dane says:

    I’ve tried your suggestion of setting expires headers for image, CSS and Javascript files. In two .htaccess files I’ve placed this:

    IN THE / DIRECTORY:
    ExpiresActive On
    ExpiresByType text/css “access plus 1 year”
    ExpiresByType text/javascript “access plus 1 year”

    IN THE /images/ DIRECTORY:
    ExpiresActive On
    ExpiresDefault “access plus 1 year”

    This works perfectly for images and CSS files, but not at all for Javascript files. Any idea why?

  4. Gr8Dane says:

    I finally discovered the solution to the problem in my previous post. I added this line to the existing ones in the / directory:

    ExpiresByType application/javascript “access plus 1 year”

    I’m not sure why this makes a difference, but it works and that’s all I care about.

  5. John Reeve says:

    The application/javascript is just another mime type that is likely used by some browsers for the javascript files. Any time a web developer starts having to deal with mime types it can become confusing because often times a type of content will have multiple mime types associated with it. For example, image/jpg and image/jpeg both refer to the JPG image format. Fortunately, web browsers deal with this example well, but getting into more infrequently used mime types, such as PDF and Word Docs, the web browsers don’t all agree on what the mime type should be.

  6. Brian says:

    of course for fingerprinting, you could even go one step further and keep it out of PHP. example:

    so we have our js and css in a build system and the filenames come out like:
    javascript_[filenumber].js and
    stylesheet_[filenumber].css

    but in our templates we output:
    javascript[TIMESTAMP]_[filenumber].js and
    stylesheet[TIMESTAMP]_[filenumber].css

    nginx rewrite rules:
    rewrite ^(.*)/javascript([0-9])*_.*\.js$ $1/javascript$2.js last;
    rewrite ^(.*)/stylesheet([0-9])*_.*\.css$ $1/stylesheet$2.css last;

    This forces the browser to automatically downloaded if the timestamp is different in our HTML, even though on the backend, it still points to the same file, just updated. No PHP processing this way, very fast.

Leave a Reply

Intervals Blog

A collection of useful tips, tales and opinions based on decades of collective experience designing and developing web sites and web-based applications.

What is Intervals?

Intervals is online time, task and project management software built by and for web designers, developers and creatives.
Learn more…

John Reeve
Author Profile
John Reeve

John is a co-founder, web designer and developer at Pelago. His blog posts are inspired by everyday encounters with designers, developers, creatives and small businesses in general. John is an avid reader and road cyclist.
» More about John
» Read posts by John

Jennifer Payne
Author Profile
Jennifer Payne

Jennifer is the Director of Quality and Efficiency at Pelago. Her blog posts are based largely on her experience working with teams to improve harmony and productivity. Jennifer is a cat person.
» More about Jennifer
» Read posts by Jennifer

Michael Payne
Author Profile
Michael Payne

Michael is a co-founder and product architect at Pelago. His contributions stem from experiences managing the development process behind web sites and web-based applications such as Intervals. Michael drives a 1990 Volkswagen Carat with a rebuilt 2.4 liter engine from GoWesty.
» More about Michael
» Read posts by Michael

help.myintervals.com
Videos, tips & tricks