Internet Explorer’s Ajax Caching: What Are YOU Going To Do About It?

It’s well known that Internet Explorer aggressively caches ajax calls whereas all the other browsers grab the data fresh every time. This is usually bad: I’ve never encountered a case where I want ajax to NOT contact the server. Firefox, Safari and the other browsers know this and don’t cache ajax calls.  It’s not illegal or anything, it’s just caching to the maximum extent permitted by the spec.

In IE9′s new F12 Developer Console (finally!), cached requests show up as 304 Not Modified with a very suspicious < 1 ms response time.  The 304 is a red herring:  the server is never contacted.  It simply means IE in its infinite wisdom has decided the response wouldn’t have been modified IF it had asked the server, and serves it from browser cache.  Debugging on the server side shows that IE is really and truly *not* hitting the server.

This is a problem on all versions of IE:   IE9, IE8, IE7, and IE6.  People were scratching their heads over it way back in 2006.

To prevent Internet Explorer from caching, you have several options:

  • Add a cache busting token to the query string, like ?date=[timestamp].  In jQuery and YUI you can tell them to do this automatically.
  • Use POST instead of a GET
  • Send a HTTP response header that specifically forbids browsers to cache it

Of these, the HTTP response header is what I consider the “correct” solution.  But I’ll go through all of them below.

Cache Buster in Query String

On every ajax request, you can add a cache busting token to the query string, like ?date=[timestamp]

If you are using JQuery or YUI (the two frameworks I use the most), you can tell THEM to add the cache-busting parameter.

JQuery even has a global parameter to disable caching — set it in your <head> (like in your site template), and you’ve just cache-busted any ajax call jQuery makes.

However, I don’t like this approach:

  • It sends unnecessary data to the server that may interfere with the request.  Like, maybe the request takes an arbitrary number of parameters and the cache busting parameter becomes one of them.
  • Also, it clutters up the browser’s cache with a bunch of content that it’ll never serve, leaving less space for the content that SHOULD be cached.

POST Instead of GET

You can send your ajax call as a POST instead of a GET.  Browsers will never cache a POST.

However, I don’t like this approach either:

  • POSTs are for calls that modify the server.  If you are retrieving information from the server without modifying anything, you should stick with a GET.
  • I’ve also heard that POSTs are slower in ajax than GETs, but that’s a very minor secondary consideration.

Set Cache Headers Correctly

This is what I’d call the “correct” approach:  when the server sends an ajax response, have it set a HTTP header that tells all browsers to NOT cache your content because, well, you don’t WANT it to be cached.

You should, anyway:  Firefox and Safari just happen to be making a convenient assumption that you don’t want your ajax calls cached, whereas IE caches as much as it can get away with.  This will make all browsers’ behavior consistent.

How to do this?  Well, setting the caching in every single one of your server calls is for the birds.  You want something that applies to all ajax calls globally.   Fortunately, most javascript libraries — including jQuery, YUI, Mootools and Prototype — send a X-Requested-With: XmlHttpRequest header on every ajax request.

In the Java world, you can write a response filter that detects the X-Requested-With: XmlHttpRequest header and sets a “don’t cache this” header.

But in the Groovy world of Grails, there’s something even easier (of course). Below is a Grails filter that prevents caching of ajax requests that identify themselves with the X-Requested-With: XmlHttpRequest:

// put this class in grails-app/config/
class AjaxFilters {
    def filters = {
        all(controller:'*', action:'*') {
            before = {
                if (request.getHeader('X-Requested-With')?.equals('XMLHttpRequest')) {
                    response.setHeader('Expires', '-1')
                }
            }
        }
    }
}

Some people prefer to use the Cache-Control: no-cache header instead of expires. Here’s the difference:

  • Cache-Control: no-cache – absolutely NO caching
  • Expires: -1 – the browser “usually” contacts the Web server for updates to that page via a conditional If-Modified-Since request. However, the page remains in the disk cache and is used in appropriate situations without contacting the remote Web server, such as when the BACK and FORWARD buttons are used to access the navigation history or when the browser is in offline mode.

By adding this filter, you make Internet Explorer’s caching consistent with what Firefox and Safari already do.

  • Pingback: Microsoft Internet Explorer cashes Ajax calls | user.wordpress.com

  • http://www.facebook.com/people/Michael-Murphy/779630186 Michael Murphy

    I have this code below. How would I change this with code above?
    @header(“Cache-Control: no-cache, must-revalidate”);@header(“Pragma: no-cache”);

  • German Vicencio

    This is my favorite post in the whole Internet so far in the year, thank you.