Much has been written on the advantages of using a Content Delivery Network (CDN) to deliver static content ( jQuery, images, CSS files, etc.). (If you’re not familiar with CDNs, read ScottGu’s blog post Announcing the Microsoft AJAX CDN and Microsoft Ajax Content Delivery Network ) But the biggest advantages of CDNs are often overlooked:
I’ll use the Fiddler tool and my MVC Movie sample to demonstrate the advantages of using a CDN.
There are two principal mechanisms to browser caching:
Browsers use a freshness heuristic to determine if they should validate a resource with the server or just pull it from the cache. If you clear your browser cache and then hit my Movie sample, several static resources will be downloaded to the client cache ( JavaScript, CSS and images). Using IE9, chrome and FireFox, you can hit the Movie web site for the next couple days and the browser will serve these files from the browser specific cache without even checking with the server. The browser will cache these static files without validating them with the server unless one of the following is true.
The first and last modernizer files were served by Cassini (The default Visual Studio Web Server) or IIS Express, that’s why we see the port number. The middle two were served by the local machines IIS server, but using two different host names (localserver and the actual name, q1). You can examine the IE9 cache by selecting the Settings button under Browsing history on the General tab, then selecting View Files.
Each browser has its own cache, so FireFox won’t use files cached by Chrome or IE.
The following image shows a Fiddler session of browsing to my Movie site. Because I hadn’t browsed to the Movie site in several days, IE9 was forced to validate the modernizr and the custom jQuery file.
Selecting the Caching Tab in Fiddler gives the details on why validation was necessary and how much longer browsers will server these files directly without server validation. In the example below, for the next 2 days, 19 hours and 20 minutes, IE9 will pull the resource directly from the cache without checking with the server (and saving the HTTP 304 round trip). The Caching Inspector in Fiddler will show you when a response expires, based on the headers provided on that response. For instance, here’s the default response from IIS 7.5 which contains an ETAG and Last-Modified header, but no expiration information:
The No Explicit HTTP Expiration information was provided message is a good hint of what you need to do, explicitly set the expiration. Best practices recommend that web developers should specify an explicit expiration time for their content several years out in order to ensure that the browser is able to reuse the content without making conditional HTTP requests to revalidate the content with the server. If the resource changes, change the name of the resource. The following markup shows the contents of a Web.config file added to the Content and Scripts folders.
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <staticContent> <clientCache cacheControlMode="UseExpires" httpExpires="Mon, 06 May 2013 00:00:00 GMT" /> </staticContent> </system.webServer> </configuration>
This sets the Expires Header out a couple years. Using ^R in the IE9 F-12 developer tools, clear the cache, then browse to the Movie site. Using Fiddler we can see the Expires Header will allow IE9 to serve this file directly from the cache without checking with the server for the next two years.
Fiddler correctly shows IE did not make a conditional requests for the static resources, that is, there are no HTTP GET requests from IE and no HTTP 304 responses from the server. Why is IE9 showing GET requests made by IE9 and the server returning 304’s? Eric Lawrence explains why 13 minutes into his presentation Debugging with Fiddler. It is difficult, at the level that the F12 Network Monitor is installed, to determine whether a given “from cache” response was “PreNetIO” (e.g. fresh in the local cache) or “PostNetIO” (e.g. in the local cache but a conditional HTTP request was used to validate freshness). Hence, sometimes F12 will show misleading “(304)”s when it meant “(cache)”.
Firebug is actually worse, showing expensive HTTP 200 results for each of the static resources.
Chrome developer tools correctly show each resource coming from the cache.
The following code shows the layout file in my modified MVC Movie project which uses the LoadRes helper to load static and CDN resources.
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>@ViewBag.Title</title> @LoadRes("Site.css") @LoadRes("http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.11/themes/redmond/jquery-ui.css") @LoadRes("modernizr-1.7.min.js") @LoadRes("http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.1.min.js") @LoadRes("http://ajax.aspnetcdn.com/ajax/jquery.validate/1.5.5/jquery.validate.min.js") @LoadRes("http://ajax.aspnetcdn.com/ajax/mvc/3.0/jquery.validate.unobtrusive.min.js") @LoadRes("jquery-ui-1.8.11.custom.min.js") </head> <body> <div class="page"> <header> <div id="title"> <h1>MovieLT</h1> </div> <div id="logindisplay"> No Login </div> <nav> <ul id="menu"> <li>@Html.ActionLink("Home", "Index", "Movies")</li> <li>@Html.ActionLink("About", "SearchIndex", "Movies")</li> </ul> </nav> </header> <section id="main"> @RenderBody() </section> <footer> </footer> </div> </body> </html> @helper LoadRes(string sFile) { // Not CDN but JavaScript if (!sFile.Contains("http://") && sFile.EndsWith(".js")) { <script src="@Url.Content("~/Scripts/" + sFile)" type="text/javascript"></script> } // CDN and JavaScript else if (sFile.Contains("http://") && sFile.EndsWith(".js")) { <script src="@sFile" type="text/javascript"></script> } // CDN and CSS else if (sFile.Contains("http://") && sFile.EndsWith(".css")) { <link href="@sFile" rel="stylesheet" type="text/css" /> // Not CDN but CSS } else if (sFile.EndsWith(".css")) { <link href="@Url.Content("~/Content/" + sFile)" rel="stylesheet" type="text/css" /> } }
I use my LoadRes helper to clean up the markup used to load resources.
Special thanks to Erick Lawrence for answering questions. Much of the information came from his blog.
Good Links