Introduction
It's a good practice to use many small JavaScript and CSS files instead of one large JavaScript/CSS file for better code maintainability, but bad in terms of website performance. Although you should write your JavaScript code in small files and break large CSS files into small chunks, when a browser requests those JavaScript and CSS files, it makes one HTTP request per file. Every HTTP request results in a network roundtrip from your browser to the server and the delay in reaching the server and coming back to the browser is called latency. So, if you have four JavaScripts and three CSS files loaded by a page, you are wasting time in seven network roundtrips. Within the USA, latency is average 70ms. So, you waste 7x70 = 490ms, about half a second of delay. Outside USA, average latency is around 200ms. So, that means 1400ms of waiting. The browser cannot show the page properly until CSS and JavaScripts are fully loaded. So, the more latency you have, the slower the page loads.
How Bad is Latency
Here's a graph that shows how each request latency adds up and introduces significant delay in page loading:

You can reduce the wait time by using a CDN. Read my previous blog post about using CDN. However, a better solution is to deliver multiple files over one request using an HttpHandler that combines several files and delivers as one output. So, instead of putting many <script> or <link> tags, you just put one <script> and one <link> tag, and point them to the HttpHandler. You tell the handler which files to combine and it delivers those files in one response. This saves browser from making many requests and eliminates the latency.
Here you can see how much improvement you get if you can combine multiple JavaScripts and CSS into one.
In a typical web page, you will see many JavaScripts referenced:
<script type="text/javascript" src="http://www.msmvps.com/Content/JScript/jquery.js">
</script>
<script type="text/javascript" src="http://www.msmvps.com/Content/JScript/jDate.js">
</script>
<script type="text/javascript"
src="http://www.msmvps.com/Content/JScript/jQuery.Core.js">
</script>
<script type="text/javascript"
src="http://www.msmvps.com/Content/JScript/jQuery.Delegate.js">
</script>
<script type="text/javascript"
src="http://www.msmvps.com/Content/JScript/jQuery.Validation.js">
</script>
Instead of these individual <script> tags, you can use only one <script> tag to serve the whole set of scripts using an Http Handler:
<script type="text/javascript"
src="HttpCombiner.ashx?s=jQueryScripts&t=text/javascript&v=1" >
</script>
The HTTP Handler reads the file names defined in a configuration and combines all those files and delivers them as one response. It delivers the response as gzip compressed to save bandwidth. Moreover, it generates a proper cache header to cache the response in the browser cache, so that, the browser does not request it again on future visits.
In the query string, you specify the file set name in the "s" parameter, then the content type in the "t" parameter and a version number in "v" parameter. As the response is cached, if you make changes to any of the files in the set, you will have to increase the "v" parameter value to make browsers download the response again.
Using this HttpHandler, you can deliver CSS as well:
<link type="text/css" rel="stylesheet"
href="HttpCombiner.ashx?s=CommonCss&t=text/css&v=1" ></link>
Here's how you define the sets in web.config:
<appSettings>
<add key="jQueryScripts"
value="~/Content/JScript/jquery.js,
~/Content/JScript/jDate.js,
~/Content/JScript/jQuery.Core.js,
~/Content/JScript/jQuery.Delegate.js,
~/Content/JScript/jQuery.Validation.js"
/>
<add key="CommonCss"
value="~/App_Themes/Default/Theme.css,
~/Css/Common.css,
~/Controls/Grid/grid.css"
/>
</appSettings>
Example Website that Uses HttpCombiner
I have made a simple test website to show you the use of HttpCombiner. The test website has two CSS and two JS files. The default.aspx requests both of them using only one <link> and <script> tag via the HttpCombiner.ashx.
Here's the content of the Default.aspx:

Here you see, there's one <link> tag sending a request to HttpCombiner.ashx to deliver the set named Set_Css and a <script> tag asking for a set Set_Javascript.
Files that belong to these two sets are defined in the web.config file:
Here's how the handler works:
- First it reads the file set name passed in the "s" parameter
- Then it gets the files defined in the web.config for the set
- It reads individual files and stores in a buffer
- The buffer is then gzip compressed
- The compressed buffer is sent to the browser
- The compressed buffer is stored in ASP.NET cache so that subsequent requests for the same set can be directly served from cache without reading the individual files from file system or external URL
Benefits of the handler:
- It saves network roundtrip. The more files you can put in one set, the more you save in network latency. This improves performance.
- It caches the total combined response as compressed and thus saves reading file from file system and compressing it again and again. This improves scalability.
How the HttpHandler Works
First the handler reads the set, type and version to use from the query string:

If the set has already been cached, the it's written directly from cache. Otherwise the files in the set are loaded one by one and stored in a MemoryStream. The MemoryStream is compressed using GZipStream if browser supports compressed output.

After combining all the files and compressing it, the combined bytes are cached so that subsequent requests can be directly served from cache.

The GetFileBytes function reads a file or URL and returns the bytes. So, you can use Virtual Path to files within your website or you can use URL to external Javascript/CSS files hosted on another domain.

The WriteBytes function has much wisdom in it. It generates a proper header based on whether the bytes are in compressed form or not. Then it generates proper browser cache header to make browser cache the response.

How to use this handler::
- Include the
HttpCombiner.ashx in your project
- Define the file sets in the
<appSettings> section of your web.config
- Change
<link> and <script> tags throughout your website to point to HttpCombiner.ashx in this format:
HttpCombiner.ashx?s=<setName>&t=<contentType>&v=<versionNo>
Conclusion
That's it! Make your website faster to load, get more users and earn more revenue.
You should also read my earlier articles about deferring and combining script loading for faster perceived speed and how to make best use of browser cache.
|
|
 |
 | Not working with Url Rewriting Ravenet | 1:09 25 Dec '09 |
|
 |
Hi Expert,
This is nice idea, but when we are use the url re writing.its not working. when we are in home its working, when i move to sub menu using url rewriting its not load css and javascript.
Please give the solutions
thank you
|
|
|
|
 |
|
 |
Why does it not work?
Regards, Omar AL Zabir Visual C# MVP
|
|
|
|
 |
|
 |
Thank you for your prompt response,
Say i have added httpcombiner in master page, when I'm in home page its ok.now i'm navigating fully virtual url, when i was in that page my jquery function not working and menu styles alos gone.
i don't know y. now i'm try to use the firefug and checking path
thank you
|
|
|
|
 |
|
 |
Hi
This message i got from firefug.
The resource from this URL is not text: ...x?s=Set_Javascript&t=type/javascript&v=1
u is undefined anonymous(Object name=u, Object base_uri=Object)HttpComb...cript&v=1 (line 91) anonymous(Object name=u, Object name=nh)HttpComb...cript&v=1 (line 91) anonymous()HttpComb...cript&v=1 (line 91) anonymous()HttpComb...cript&v=1 (line 91) HttpCombiner.ashx?s=Set_Javascript&t=type/javascript&v=1()
thank you
|
|
|
|
 |
 | Minify and obfuscate with YUI Compressor Kellros | 2:59 5 Oct '09 |
|
 |
Hello,
It wouldn't be too hard to implement css/js minification with this HTTP Handler to minify and obfuscate the javascript or just minify the css for better compression (supposedly this can reduce the combined file size by 40% ish before gzip/deflate compression).
Great article Omar, been a fan of yours for just over a year now.
Links: Single file minify/obfuscate code via YUI Compressor in C# YUI Compressor
|
|
|
|
 |
 | Cannot update file after caching Member 531160 | 5:17 15 Sep '09 |
|
 |
Hi, Initially it worked fine. Then I made changes in my .js file, changed version and uploaded, but cannot see changes and get error. I addeed one function in .js file and when I click on web page, it gives me error.
Let me know how to fix. Thanks in advance, best, ubuser
|
|
|
|
 |
|
 |
your old js is cached in browser. that's why it's not downloading anymore.
You need to add some version suffix to the url.
Regards, Omar AL Zabir Visual C# MVP
|
|
|
|
 |
|
 |
Hi Omar, I did try to change version. Initially I was using version number like 2009.09.14.05 and did not work. Then I changed version number like 22 (two digit number) and still same problem. Basically my intention was that if I change file in future then I can change version number to 2009.09.15.01 (today's date 2009.09.15.xx) so I don't get caching problem. You think using version number like 2009.09.14.05 causing problem? And if so, how can I remove from cache and refresh with updated file manually?
I appreciate for your quick response. Let me know.
Best, ubuser
|
|
|
|
 |
 | Medium Trust Zach Floyd | 7:21 4 Sep '09 |
|
 |
First off I'd like to say nice work. Second, your solution doesn't work in Medium trust, but the fix appears to be very simple. In your "WriteBytes" method, simply change:
response.OutputStream.Write(bytes, 0, bytes.Length); response.Flush();
to
context.Response.BinaryWrite(bytes);
|
|
|
|
 |
 | What is the difference between Scripts.ashx & HttpCombiner.ashx? Member 1985165 | 1:00 6 Jun '09 |
|
 |
Hi,
I'm planning to combine my js files as well as the script files downloaded by ScriptManager. I would like to know the difference between Scripts.ashx and HttpCombiner.ashx. Both of them are used to combine scripts. Do I need to use both Scripts.ashx for asp.net ajax script files and HttpCombiner for my own custom scripts or I can simply use HttpCombiner to combine both asp.net ajax script and my custom scripts.
Thanks -Sunil
|
|
|
|
 |
 | firefox 3 issues with combining CSS files swyche | 6:51 6 Feb '09 |
|
 |
Combining CSS files has an issue that results in the first CSS selector of each CSS file after the first one to be ignored by Firefox. It shows up as a "space" when you look at the styles in Firebug but does not display when you view CSS using the Web Developer toolbar. Thus it seems to be a hidden character affecting it.
The only workaround I can think of to do is to place a dummy CSS selector that does nothing as the first item in each CSS file to be combined after the first file.
|
|
|
|
 |
|
 |
have the same problem ...
shlomi .
|
|
|
|
 |
 | Dynamic list of files kitkatrobins | 23:35 18 Jan '09 |
|
 |
How would you build the list of needed files dynamically? For instance if a page has 5 scripts files (say 30k worth) you only want it on that page, how would you locate this in the handler? You could put them in the page (e.g. override base class of something like string[] GetFiles()) but how would get this information into the http handler?
|
|
|
|
 |
 | Unable to download the code maxcom | 23:22 5 Dec '08 |
|
 |
Hi,
It's really awesome way to increase the performance. The link to download the code is not working. Would you please help with that.
Thanks, Mahesh
max
|
|
|
|
 |
 | How to implement your code if using friendly adapters and ajax net framework? Member 3257275 | 13:58 11 Sep '08 |
|
 |
It is possible? ideas?
Thank you!
|
|
|
|
 |
 | Real Great cwp42 | 23:23 4 Sep '08 |
|
|
 |
 | Are there any benchmarks? Filipe Martins | 6:43 3 Sep '08 |
|
 |
Congratulations for writing about this issue. I myself have been wondering about it for sometime, but I always run into many doubts about if a single big file is really better than a well broken set of smaller ones.
That's because TCP is an asynchronous protocol, meaning that the browser probably opens many parallel connections to the server for downloading each separate file. I speculate that that timespan is quite lower then summing the time spent on each download in isolation. I'm sure we all remember that technique of breaking a big image in a bunch of smaller ones just for optimizing the page rendering speed...
Other issue is caching. Consider that a three page site has a "page1" downloading "JS1", "page2" downloading "JS2" and "page3" downloading both. Pages 1 and 2 just need downloading and parsing relatively small files, and "page3" may build on that. But if all the pages reference a big file, then all of them must download it at least once and parse it every time. More, if a user only visits "page1" or "page2" the traffic is quite less cause only "JS1" or "JS2" needs downloading and parsing.
I'm by no means trying to contradict what you said. I'm just proposing that we make some benchmarks, if there are none already. I'd really like to get to the bottom of this.
|
|
|
|
 |
|
 |
Hi Filipe, Browser has limitation of 2 connections per domain. So, it cannot spread the download over many connections and save total download time. Moreover, each connection has significant latency which adds up. On an avg, outside USA you get 200ms latency which is substancial.
Yahoo Developer Blog is a good source of all these performance research: http://developer.yahoo.com/performance/
You will see from their performance best practices, it's all about saving connections. You will find techniques where many images are combined into one using CSS Sprites. Idea is to combine as much as you can and make browser download and cache as many common stuffs as possible.
On your example, say your homepage downloads 3 javascripts - JS1, JS2 and JS3. Your other pages download another 3 javascripts where two are common - JS1, JS2, JS4. In this case, do you combine three JS and deliver on homepage and let other pages download that same three combined and one extra? Answer is, yes. Here's why:
1) Once homepage downloads the combined JS1JS2JS3, it gets cached in browser. It also saves time downloading one file over three different files as browsers download one script at a time. It does not parallelize. 2) When second page requests JS1JS2JS3, it gets the set from browser's cache. So, it only downloads JS4 from server.
Now, if you were downloading three of them independently, you would be introducing delay on your homepage where performance matters most because users come to your homepage most of the time. So, goal is to maximize performance on first visit and on most visited page.
Regards, Omar AL Zabir Visual C# MVP
|
|
|
|
 |
 | Background Images novelProjects | 4:41 3 Sep '08 |
|
 |
Image paths in the CSS file needs to be relative to the HttpCombiner and not relative to your stylesheet.
|
|
|
|
 |
 | How about including Javascript and CSS compressor/minifier/obfuscator ? seaplanner | 1:09 3 Sep '08 |
|
 |
Although your combined files are gzipped, squeezing out all the formatting and comments will still make a performance difference. Also, I think that there are many people that would like to leave comments for development purposes but find that the hassle of running obfuscators too much - your "on the fly" technique with caching is the perfect place to do it automatically.
|
|
|
|
 |
 | Combine CSS with many different themes Member 3423545 | 4:06 2 Sep '08 |
|
 |
Hi, good job. Im a designer, and i have to implement your code. With javascript i'have no problems, but the CSS are not under my control. The CSS Theme is different for each user. How to use your code in this case ?  Exemple for css directory : User A : App_Themes >Default >style.css User B : App_Themes >BlueTheme >style.css User C : App_Themes >RedTheme >style.css User D : App_Themes >GreenTheme >style.css
The user can change dynamically the theme. Its save in the database for next connection. Thank for your reply.
|
|
|
|
 |
|
 |
I have the same need and would be curious to know the answer. Thanks.
|
|
|
|
 |
 | Development Process Member 3106188 | 5:03 1 Sep '08 |
|
 |
It is working fine, thank you very much!
Notice. When you change the CSS or JS files, you need to change the version in the Web Form, as written in the sample.
src="HttpCombiner.ashx?s=Set_Javascript&t=type/javascript&v=4" >
"...As the response is cached, if you make changes to any of the files in the set, you will have to increase the "v" parameter value to make browsers download the response again."
|
|
|
|
 |
 | Microsoft JScript runtime error object expected Member 3106188 | 8:40 31 Aug '08 |
|
 |
Hi,
I manage to run your sample. When I included the source code in my project I got following error: Microsoft JScript runtime error object expected.
I started from a empty project; do I miss a refrence?
The code stops at:
<script type="text/javascript"> $(function() { $("#jquery").html("jQuery also loaded, cool!"); }); </script>
Thank you, Rune
|
|
|
|
 |
|
 |
Hi it is okay now... It was a folder path problem... 
|
|
|
|
 |
|
|