ASP.Net Site Performance: Improving JavaScript Loading

Matt Perdeck

October 2010


ASP.NET Site Performance Secrets

ASP.NET Site Performance Secrets

Simple and proven techniques to quickly speed up your ASP.NET website

  • Speed up your ASP.NET website by identifying performance bottlenecks that hold back your site's performance and fixing them
  • Tips and tricks for writing faster code and pinpointing those areas in the code that matter most, thus saving time and energy
  • Drastically reduce page load times
  • Configure and improve compression – the single most important way to improve your site's performance
  • Written in a simple problem-solving manner – with a practical hands-on approach and just the right amount of theory you need to make sense of it all


        Read more about this book      

One approach to improving page performance is to shift functionality from the server to the browser. Instead of calculating a result or validating a form in C# on the server, you use JavaScript code on the browser. A drawback of this approach is that it involves physically moving code from the server to the browser. Because JavaScript is not compiled, it can be quite bulky. This can affect page load times, especially if you use large JavaScript libraries. You're effectively trading off increased page load times against faster response times after the page has loaded.

In this article by Matt Perdeck, author of ASP.NET Site Performance Secret, you'll see how to reduce the impact on page load times by the need to load JavaScript files. It shows:

  • How JavaScript files can block rendering of the page while they are being loaded and executed
  • How to load JavaScript in parallel with other resources
  • How to load JavaScript more quickly

(For more resources on ASP.Net, see here.)

Problem: JavaScript loading blocks page rendering

JavaScript files are static files, just as images and CSS files. However, unlike images, when a JavaScript file is loaded or executed using a <script> tag, rendering of the page is suspended.

This makes sense, because the page may contain script blocks after the <script> tag that are dependent on the JavaScript file. If loading of a JavaScript file didn't block page rendering, the other blocks could be executed before the file had loaded, leading to JavaScript errors.

Confirming with a test site

You can confirm that loading a JavaScript file blocks rendering of the page by running the website in the folder JavaScriptBlocksRendering in the downloaded code bundle. This site consists of a single page that loads a single script, script1.js. It also has a single image, chemistry.png, and a stylesheet style1.css. It uses an HTTP module that suspends the working thread for five seconds when a JavaScript file is loaded. Images and CSS files are delayed by about two seconds. When you load the page, you'll see that the page content appears after only about five seconds. Then after two seconds, the image appears, unless you use Firefox, which often loads images in parallel with the JavaScript.

If you make a Waterfall chart, you can see how the image and stylesheet are loaded after the JavaScript file, instead of in parallel:

ASP.NET Site Performance Secret

To get the delays, run the test site on IIS 7 in integrated pipeline mode. Do not use the Cassini web server built into Visual Studio.

If you find that there is no delay, clear the browser cache. If that doesn't work either, the files may be in kernel cache on the server—remove them by restarting IIS using Internet Information Services (IIS) Manager. To open IIS manager, click on Start | Control Panel, type "admin" in the search box, click on Administrative Tools, and then double-click on Internet Information Services (IIS) Manager.

Integrated/Classic Pipeline Mode
As in IIS 6, every website runs as part of an application pool in IIS 7. Each IIS 7 application pool can be switched between Integrated Pipeline Mode (the default) and Classic Pipeline Mode. In Integrated Pipeline Mode, the ASP.NET runtime is integrated with the core web server, so that the server can be managed for example, via web.config elements. In Classic Pipeline Mode, IIS 7 functions more like IIS 6, where ASP.NET runs within an ISAPI extension.

Approaches to reduce the impact on load times

Although it makes sense to suspend rendering the page while a <script> tag loads or executes JavaScript, it would still be good to minimize the time visitors have to wait for the page to appear, especially if there is a lot of JavaScript to load. Here are a few ways to do that:

  • Start loading JavaScript after other components have started loading, such as images and CSS files. That way, the other components load in parallel with the JavaScript instead of after the JavaScript, and so are available sooner when page rendering resumes.
  • Load JavaScript more quickly. Page rendering is still blocked, but for less time.
  • Load JavaScript on demand. Only load the JavaScript upfront that you need to render the page. Load the JavaScript that handles button clicks, and so on, when you need it.
  • Use specific techniques to prevent JavaScript loading from blocking rendering. This includes loading the JavaScript after the page has rendered, or in parallel with page rendering.

These approaches can be combined or used on their own for the best tradeoff between development time and performance. Let's go through each approach.

Approach: Start loading after other components

This approach aims to render the page sooner by loading CSS stylesheets and images in parallel with the JavaScript rather than after the JavaScript. That way, when the JavaScript has finished loading, the CSS and images will have finished loading too and will be ready to use; or at least it will take less time for them to finish loading after the JavaScript has loaded.

To load the CSS stylesheets and images in parallel with the JavaScript, you would start loading them before you start loading the JavaScript. In the case of CSS stylesheets that is easy—simply place their <link> tags before the <script> tags:

<link rel="Stylesheet" type="text/css" href="css/style1.css" />
<script type="text/javascript" src="js/script1.js"></script>

Starting the loading of images is slightly trickier because images are normally loaded when the page body is evaluated, not as a part of the page head.

In the test page you just saw with the image chemistry.png, you can use a bit of simple JavaScript to get the browser to start loading the image before it starts loading the JavaScript file. This is referred to as "image preloading" (page PreLoadWithJavaScript.aspx in the folder PreLoadImages in the downloaded code bundle):

<script type="text/javascript">
var img1 = new Image(); img1.src = "images/chemistry.png";
<link rel="Stylesheet" type="text/css" href="css/style1.css" />
<script type="text/javascript" src="js/script1.js"></script>

Run the page now and you'll get the following Waterfall chart:

ASP.NET Site Performance Secret

When the page is rendered after the JavaScript has loaded, the image and CSS files have already been loaded; so the image shows up right away.

A second option is to use invisible image tags at the start of the page body that preload the images. You can make the image tags invisible by using the style display:none. You would have to move the <script> tags from the page head to the page body after the invisible image tags, as shown (page PreLoadWithCss.aspx in folder PreLoadImages in the downloaded code bundle):

<div style="display:none">
<img src="images/chemistry.png" />
<script type="text/javascript" src="js/script1.js"></script>

Although the examples we've seen so far preload only one image, chemistry.png, you could easily preload multiple images. When you do, it makes sense to preload the most important images first, so that they are most likely to appear right away when the page renders. The browser loads components, such as images, in the order in which they appear in the HTML, so you'd wind up with something similar to the following code:

<script type="text/javascript">
var img1 = new Image(); img1.src = "images/important.png";
var img1 = new Image(); img2.src = "images/notsoimportant.png";
var img1 = new Image(); img3.src = "images/unimportant.png";

Approach: Loading JavaScript more quickly

The second approach is to simply spend less time loading the same JavaScript, so that visitors spend less time waiting for the page to render. There are a number of ways to achieve just that:

  • Techniques used with images, such as caching and parallel download
  • Free Content Delivery Networks
  • GZIP compression
  • Minification
  • Combining or breaking up JavaScript files
  • Removing unused code

Techniques used with images

JavaScript files are static files, just like images and CSS files. This means that many techniques that apply to images apply to JavaScript files as well, including the use of cookie-free domains, caching, and boosting parallel loading.

Free Content Delivery Networks

Serving static files from a Content Delivery Network (CDN) can greatly reduce download times, by serving the files from a server that is close to the visitor. A CDN also saves you bandwidth because the files are no longer served from your own server.

A number of companies now serve popular JavaScript libraries from their CDNs for free. Here are their details:

In ASP.NET 4.0 and later, you can get the ScriptManager control to load the ASP. NET AJAX script files from the Microsoft AJAX CDN instead of your web server, by setting the EnableCdn property to true:

<asp:ScriptManager ID="ScriptManager1"
EnableCdn="true" runat="server" />

One issue with loading libraries from a CDN is that it creates another point of failure—if the CDN goes down, your site is crippled.

GZIP compression

IIS has the ability to compress content sent to the browser, including JavaScript and CSS files.

Compression can make a dramatic difference to a JavaScript file as it goes over the wire from the server to the browser. Take for example the production version of the jQuery library:


Uncompressed Compressed
jQuery library 78 KB 26 KB


Compression for static files is enabled by default in IIS 7. This immediately benefits CSS files. It should also immediately benefit JavaScript files, but it doesn't because of a quirk in the default configuration of IIS 7.

Not all static files benefit from compression; for example JPEG, PNG, and GIF files are already inherently compressed because of their format. To cater to this, the IIS 7 configuration file applicationHost.config contains a list of mime types that get compressed when static compression is enabled:

<add mimeType="text/*" enabled="true" />
<add mimeType="message/*" enabled="true" />
<add mimeType="application/javascript" enabled="true" />
<add mimeType="*/*" enabled="false" />

To allow IIS to figure out what mime type a particular file has, applicationHost.config also contains default mappings from file extensions to mime types, including this one:

<staticContent lockAttributes="isDocFooterFileName">
<mimeMap fileExtension=".js"
mimeType="application/x-javascript" />

If you look closely, you'll see that the .js extension is mapped by default to a mime type that isn't in the list of mime types to be compressed when static file compression is enabled.

The easiest way to solve this is to modify your site's web.config, so that it maps the extension .js to mime type text/javascript. This matches text/* in the list of mime types to be compressed. So, IIS 7 will now compress JavaScript files with the extension .js (folder Minify in the downloaded code bundle):

<remove fileExtension=".js" />
<mimeMap fileExtension=".js" mimeType="text/javascript" />

Keep in mind that IIS 7 only applies static compression to files that are "frequently" requested. This means that the first time you request a file, it won't be compressed! Refresh the page a couple of times and compression will kick in.

        Read more about this book      

(For more resources on ASP.Net, see here.)

Minifying a JavaScript file

JavaScript files tend to contain comments and white space that make the file more readable for humans, but these are superfluous as far as the browser is concerned. Minifying a JavaScript file gets rid of those comments and white space, thereby reducing its size and download time. It also encourages developers to write more comments, because they know they won't add to the size of the file as it goes over the wire.

Some minification tools also shorten variable names. This is useful if your server does not use GZIP compression. If compression is enabled, shortening variable names will have little effect, because the compression algorithm itself is very good at shortening repeating strings, such as variable names.


Here are a number of tools that will minify a JavaScript file for you:

Impact of minification

How much effect does minification have compared with GZIP compression? Here are the results of applying GZIP compression and minification (using YUI Compressor) on the uncompressed debug version of the jQuery library:


Minified Reduction through minification
No Yes
GZIP No 22 KB 78 KB 65 percent
Compressed Yes 48 KB 26 KB 46 percent


This shows that minification has the most dramatic impact if GZIP compression has been disabled on the server (which is not recommended). Still, even with compression enabled, minification makes sense, as the drop from 48 KB to 26 KB is significant.

Implementing minification

A common way to implement minification is to run a minifier program during a release build that minifies the JavaScript files. However, this means that you can't simply copy updated JavaScript files to the production server without going through the minification step.

An alternative is to place JavaScript files unminified in production, and have the website minify the files on the fly before serving them to the browser. To save CPU cycles, you would cache the minified file. To ensure that the site immediately picks up a new version of the file, the cached file would have a file dependency on the physical (uncompressed) file on disk.

HTTP handler

You can implement this approach with an HTTP Handler. This is simply a class derived from IHttpHandler, which you put in the App_Code folder, or in a separate class library. To do the actual minification, you can use the YUI Compressor for .NET library, which you saw earlier. The result is code as shown (folder Minify in downloaded code bundle):

using System.IO;
using System.Web.UI.WebControls;
using System.Web.Caching;

Import the YUI Compressor namespace. To make this work, download the DLL at and add a reference to that DLL:

using Yahoo.Yui.Compressor;
using System.Web.UI;

namespace MinifyingHttpHandler
public class JavaScriptHttpHandler : IHttpHandler
public bool IsReusable
get { return true; }
public void ProcessRequest(HttpContext context)
const string cacheKeyPrefix = "js__";
string compressedText = null;
string uncompressedText = null;

Get the URL of the JavaScript file that this handler needs to produce. The PathAndQuery component will be used as the cache key:

Uri url = context.Request.Url;
string path = url.PathAndQuery;

Try to retrieve the minified JavaScript from cache:

string cacheKey = cacheKeyPrefix + path;
compressedText = (string)context.Cache[cacheKey];

if (compressedText == null)

If the minified JavaScript could not be found, first read the contents of the file from disk:

string filePath = context.Server.MapPath(path);
uncompressedText = File.ReadAllText(filePath);

Then, minify the contents:

compressedText = JavaScriptCompressor.Compress(uncompressedText);

Finally, cache the minified content. Use a file dependency, so that the cache manager automatically removes the content from cache if the file on disk changes:

  CacheDependency cd = new CacheDependency(filePath);
context.Cache.Insert(cacheKey, compressedText, cd);

Add a Cache-Control header to ask the browser to cache the JavaScript file for 31,536,000 seconds (365 days). You may want to reduce this if it is critical that browsers quickly pick up changes in your JavaScript.


Specify the correct content type:

context.Response.AddHeader("Content-Type", "text/javascript");

Send the actual minified content to the browser. If compressedText is null, which could happen if an exception occurred, send compressedText instead. And if that is null too, send the empty string:

     context.Response.Write(compressedText ??
(uncompressedText ?? ""));

Configuring the handler in web.config

After you've created the HTTP handler, configure it in web.config, so that ASP.NET uses it to handle requests for JavaScript files.

If you use IIS 7 in integrated pipeline mode, simply add your new handler to the handlers section of system.webServer:

<add name="Minifying" verb="*" path="*.js"
MinifyingHttpHandler" resourceType="File"/>

If you use IIS 6 or IIS 7 in classic mode, add your handler to httpHandlers instead. In that case, don't forget to update your IIS configuration, so that requests for .js files are handled by ASP.NET:

<add verb="*" path="*.js"
MinifyingHttpHandler" />

Enabling GZIP compression for dynamic files

Now that you're using ASP.NET to produce the JavaScript, you need to enable compression for dynamic files to have your JavaScript compressed by the server.

Combining or breaking up

A popular approach to reducing JavaScript load times is to combine several JavaScript files into a single big file, so only that one big file is loaded. An alternative is to do the reverse and break up a big JavaScript file into several smaller files that are loaded in parallel. Here is when each of these approaches makes sense.

When and why to combine

Take for example, a page loading four scripts:

<script type="text/javascript" src="script1.js"></script>
<script type="text/javascript" src="script2.js"></script>
<script type="text/javascript" src="script3.js"></script>
<script type="text/javascript" src="script4.js"></script>

Older browsers such as Internet Explorer 6 load the scripts one after the other. That way, the browser can be sure that if a script executes code that relies on a previous script, there will be no JavaScript errors. For example, loading the test page in IE 6 resulted in this Waterfall chart:

ASP.NET Site Performance Secret

In this scenario, it is attractive to combine files. This is because loading involves a lot more than just loading the bytes in the file. It also involves overhead, such as sending the request from the browser to the server. A site that requests four files one after the other spends four times as much time on the overhead as a site that places a single request for a single large file.

When and why to break up

Loading JavaScript files one after the other may be very safe, but is also wasteful. The browser could load files in parallel, but hold off executing them until the previous files have loaded. Newer browsers, such as Internet Explorer 7 and 8, Firefox, and Google Chrome do exactly the same thing. For example, loading the test page in IE 7 resulted in the following Waterfall chart:

ASP.NET Site Performance Secret

This makes it attractive to consider splitting large JavaScript files into a number of smaller ones, and loading them in parallel. The request overhead would then be incurred in parallel, rather than sequentially. Loading two half-sized files in parallel may take slightly over half the time taken by the single original file, depending on the time taken by the request overhead.

If you load many JavaScript, CSS, and image files in parallel, keep in mind that modern browsers have a download limit of six files per domain.

Measuring each scenario

To investigate this a bit further, I ran tests with two simple test pages—a page separate.html that loads four JavaScript files and a page combined.html that loads a single big JavaScript file, in which the four separate files are combined. A bit of JavaScript in the HTML files measured the total download time. See folder ScriptLoadTest in the downloaded code bundle.

Because the sizes of the JavaScript files might affect the results, I ran a "big" scenario with one 220-KB JavaScript file and three 37-KB files, and a "small" scenario with four 10-KB files. The test pages sat on my hosting account. Your numbers are bound to vary, but you'll get a general idea.

The results for the big scenario were:

Total download times (seconds) combined.html separate.html
IE 6 1.3 1.5
IE 7 1.4 0.9
IE 8 1.3 0.7
Firefox 3 1.4 1.2
Google Chrome 5 1.5 1


The results for the small scenario were:

Total download times (seconds) combined.html separate.html
IE 6 0.2 0.8
IE 7 0.4 0.4
IE 8 0.4 0.4
Firefox 3 0.6 0.2
Google Chrome 5 0.2 0.2


The moral of this story is to find out which browsers are most used by your target groups, and then experiment with different scenarios using those browsers.

Preventing 304 messages

When running your own tests, keep in mind that the browser will store the JavaScript files in its cache. As a result, if you press Ctrl + F5 to reload the page, some browsers, including Internet Explorer, will send If-Modified-Since and If-None-Match headers to the server. If you haven't changed the files, the server will then respond with a short 304 message indicating that the browser cache is still up to date, instead of a longer 200 message that contains the entire file. This will skew your results.

You can prevent this by running the Fiddler developers proxy ( Its filter feature allows you to neutralize the If-Modified-Since and If-None-Match headers, as shown in the following screenshot:

ASP.NET Site Performance Secret

Implementing automatic file combining

If you decide to combine your files, you could do so manually using a simple text editor. However, that would create a maintenance headache. You could also combine the files as part of the build process, but then you would have to modify your script tags as well.

The best solution would be to have the site combine the files at runtime. It can then also take care of replacing the individual script tags with their counterparts for the combined files.

Here are a number of packages you can add to your site to make this happen:

  • ASP.NET ScriptManager Control
  • Compression (deflate) and HTML, CSS, JS Minification in ASP.NET
  • Combres 2.0
  • FileCombine

Because CSS files can also benefit from being combined, these packages also support combining CSS files, except for the ASP.NET ScriptManager Control.

ASP.NET ScriptManager Control

You include the ASP.NET ScriptManager control on your pages to use ASP.NET AJAX controls, such as UpdatePanel. It automatically generates the script tags for the JavaScript files needed for those controls. It can also create script tags for other JavaScript files you nominate.

As of ASP.NET 3.5, you can tell the ScriptManager control to combine JavaScript files. It then generates the script tag for the combined file and combines the files on the fly. It also compresses the combined files using GZIP if the browser is not Internet Explorer 6. For example:

<asp:ScriptManager runat="server">
<asp:ScriptReference Path="js/script1.js" />
<asp:ScriptReference Path="js/script2.js" />

If you want to use the ScriptManager to combine your files without loading the ASP.NET Ajax libraries, use the ScriptManager derivative shown in:

This is probably the simplest way to combine script files automatically. However, a drawback of ScriptManager is that it doesn't work with CSS files. Also, it doesn't minify your JavaScript files, and you have to modify your .aspx files to make it work. Finally, the URLs of the combined files contain query strings, which means that you miss out on kernel caching and caching in most proxies.

Compression (deflate) and HTML, CSS, JS Minification in ASP.NET

Description and download is available at:

This package combines JavaScript files and CSS files. It also minifies JavaScript and CSS, and the HTML on the page as well. It compresses your files using the deflate algorithm. And because it uses a custom filter to intercept your HTML to find the script and link tags, there is no need to modify your .aspx files.

On the other hand, it uses script and CSS file URLs that contain query strings, which is not good for caching. And those query strings contain the names of the individual files that make up the combined file, which means that they can get very long. The biggest drawback is that it will break your CSS code if your CSS files are in a separate folder and you use relative image URLs, as in the following code:

background-image: url(../images/chemistry.png);

This is because the combined CSS file is not guaranteed to be served from the same folder, and so the relative URLs no longer refer to the same location.

Combres 2.0

Description and download is available at:

This highly configurable, open source package allows you to organize JavaScript and CSS files into separate resource sets. It automatically detects changes to the files and includes a version number in the combined file URLs, so a browser will never use an outdated file from its cache. It lets you combine, minify, and compress files using either GZIP or deflate. It generates Cache-Control and ETAG headers. It also supports debugging mode, where files are not minified or cached.

The only drawback to this impressive package is that you can't simply drop it into your website. You have to set up a configuration file to make it work, and you have to maintain that file as your site changes.


You'll find the project FileCombine in the folder FileCombine in the downloaded code bundle.

This package combines many of the features of Combres with the ability to simply drop it in your site without having to do configuration:

  • Combines and minifies both CSS and JavaScript files. To reduce CPU usage, the minified and combined files are kept in server cache. To make sure that you don't serve outdated files from the server cache, a cache entry is removed the moment one of the constituent individual files is updated.
  • Limits URL length of combined files by using an MD5 hash over the individual URLs as the combined file URL. The alternative of making up the combined file URL by stringing together the URLs of the constituent files can result in an extremely long URL.
  • The hashing algorithm always produces the same hash for the same constituent file URLs, as long as they are in the same order. That way, if you use the same script and CSS link tags in different pages, the browser needs to load the combined file only once, and can then retrieve it from cache for subsequent pages.
  • The URL of the combined file includes a shortened version of the last modified time of the youngest constituent file. This stops the browser from using an outdated version from its cache.
  • To improve proxy caching, no query strings are used in the URLs of combined files.
  • It assigns far-future Cache-Control headers, so that if the combined files remain unchanged, they stay as long as possible in browser and proxy cache.
  • It replaces relative url() properties in CSS files with their absolute counterparts, so that they do not break when used in a combined CSS file.
  • It also detects if the site is in debug mode. If in debug mode, it doesn't minify or combine files, and uses Cache-Control headers that specify immediate expiry from the browser cache.

Limitations of this package:

  • It considers only script tags and CSS link tags in the head of the page.
  • It doesn't minify inline script or CSS.
  • It relies on the server to do GZIP compression. To have your JavaScript and CSS files GZIP-compressed (highly recommended), enable dynamic file compression on the server.

For updates about FileCombine, search Google for "FileCombine".

The installation instructions are in the FileCombine folder in the downloaded code bundle.

Removing unused code

Because JavaScript code is separate from the HTML user interface elements it supports, it is easy to forget to remove code when it is no longer needed. As a result, your JavaScript files may become bloated with unused code.

Here are a number of tools that help identify unused code. Be careful with this— JavaScript is a very dynamic language, making it difficult for a tool to make sure that a piece of code really is unused.

  • JSLint
    It statically analyzes your JavaScript and reports bad programming practices. It also reports unreachable code and unused variables.
  • Jsure
    This is another program that statically analyzes your JavaScript. It reports unused functions, variables, and function arguments.
  • JSCoverage
    JSCoverage shows which lines of a program get executed, and which ones are missed. It works by instrumenting the JavaScript code used in web pages. Code coverage statistics are collected while the instrumented JavaScript code is executed in a web browser.


In this article, we saw how page rendering is blocked by JavaScript <script> tags. This was followed by ways to load other components such as CSS stylesheets and images in parallel with the JavaScript to reduce the overall time taken to load the page fully. We then saw how to load JavaScript files more quickly, including minification, free CDNs, removing unused code, and combining or breaking up JavaScript files.

In the next article, ASP.Net Site Performance: Reducing Page Load Time, we will see how to load JavaScript code on demand and without blocking page rendering.

Further resources on this subject:

You've been reading an excerpt of:

ASP.NET Site Performance Secrets

Explore Title