Reducing the number of page components
Dec 5 This is the fifth in the series of performance articles as part of my 2009 performance advent calendar experiment. Stay tuned for the next articles.
Let's talk a but about waterfall optimization - the first thing that happens in Mr.Page's life. The best way to optimize and speed up the waterfall is to have less stuff in it. The fewer page components, the faster the page - simple as that.
Fewer components vs. component weight
The size of the page components, meaning their size in kB, is important. It makes sense - smaller pages will load faster, 100K JavaScript will load faster than 150K. It's important to keep sizes low, but it should be clear that the number of components is even more important than their filesize.
Why? Because every HTTP request has overhead.
OK, but how bad can it be, someone might ask. If you look at an HTTP request - it has a header and a body. A 100K body will greatly outweigh the size of the headers, no matter how bloated they are.
Here's the headers for a request to Yahoo! Search:
Host: search.yahoo.com User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5;) Firefox/3.5.5 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive
To that request the server responds with the body (content) of the response prepended with some headers like:
HTTP/1.1 200 OK Date: Sat, 05 Dec 2009 07:36:25 GMT P3P: policyref="http://p3p.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR... blah, blah" Set-Cookie: sSN=nTMt3Lo2...crazy stuff...nLvwVxUU; path=/;domain=.search.yahoo.com Cache-Control: private Connection: close Content-Type: text/html; charset=ISO-8859-1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html lang="en"><head><meta...
This is 352 bytes request header and 495 bytes response header. Not so bad, eh? No matter how hard you try to make your cookies monster-size, the response body (9k gzipped in this case) will always be significantly bigger. So what's the problem with the overhead of the HTTP requests then?
The size of the headers is a problem when you make requests for small components - say requests for small icons - for example 1K or under. In this case you exchange 1K headers to get 1K of useful data to present to the user. Clearly a waste. Additionally this 1K of headers can grow once you start writing more cookies. It may very well happen that the HTTP headers size is bigger than the actual icon you need. And even if the headers are not bigger that the component, they are still big when you think percent-wise. 1K of 10K is 10%.
But the HTTP headers size is only one (and the smaller) of the problems.
The bigger problem is the HTTP connection overhead.
HTTP connection overhead
What happens (at a high level) when you type a URL and hit Enter? The browser sends a request to the server. Which server? The browser needs to know the IP address of the server, so if it doesn't have it in the cache, it makes a DNS lookup. Than the browser establishes a connection to the server. Then it waits for the first byte of the response from the server. Then it receives the full response (payload).
Here's how this looks like graphically represented by webpagetest.org
![]()
And the color legend:
- DNS lookup
- Initial connection
- TTFB (Time to first byte)
- Payload
So what do we have here - looks like in this particular case the browser is downloading content about 40% of the time. The rest of the time it's... well, not downloading content. How's that for an overhead. And the part of not downloading can be even greater, this above was just one example.
Now how about this - a bird-eye view of the 4 last pages in webpagetest.org's test history - just some random pages people have tested.

Do you see a lot of blue (time spent downloading content). Not as much as you would've hoped. There's some DNS lookups, some orange... and OMG, talk about going green!
In fact you may notice that the smaller the component, the smaller is the blue part.
What does all this tell us?
- A significant chunk of time is spent in activities other than downloading.
- Smaller components still incur HTTP overhead and for them the relative penalty (relative to their size) is atrocious.
So what's a performance optimizer to do? Reduce the number of components and so pay fewer penalties.
Just remove stuff
Truth is - a lot of stuff on the pages today is not needed. Features no one likes or uses clutter the page and make it heavier. Well, what can you do, the boss/client/marketing guy wants that feature there. What you can do is at least try. You can introduce some science into the marketing activities - measure how much a specific feature is used. Or if you already have the data - look at it. Decide what can a page go without.
It's going to be tough convincing people to remove stuff. After all you spend time developing it. Someone dreamt up that feature initially. Someone (not the users) loves it. People hate to let go. But still, it's worth to try.
Combine components
Now that the phase of convincing people to remove stuff is done, what's left needs to be combined. How do you combine components? Simple - all JavaScripts go into a single file, all CSS into a single file. All decoration images go into a sprite.
JavaScript example (from a page to remain anonymous)
Before:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js"></script> <script src="/javascripts/application.js?1258423604"></script> <script src="/javascripts/ui/minified/jquery.ui.all.min.js?1258423604"></script> <script src="/javascripts/ui-ext/ui.bgiframe.min.js?1258423604"></script> <script src="/javascripts/ui-ext/ui.stars.pack.js?1258423604"></script> <script src="/javascripts/ui-ext/ui.dimensions.js?1258423604"></script> <script src="/javascripts/ext/jquery.form.min.js?1258423604"></script>
After:
<script src="/javascripts/all.js"></script>
Sizes, gzipped: 70029 bytes before, 65194 bytes after. Simply merging files and there's even 6.9% saving!
And the more important saving: 6 less HTTP requests
Repeat for CSS. Before:
/stylesheets/general.css?1258423604 /stylesheets/global.css /stylesheets/ui.stars.css /stylesheets/themes/enation/enation.all.css /public/template/css/132/1245869225
After:
<link type="text/css" rel="stylesheet" href="/stylesheets/all.css" />
Sizes, gzipped: before 14781 bytes, after 13352 bytes, saving 9.6%.
But the bigger saving: 4 less HTTP requests.
If you wonder how come the sizes before and after are different since we merely concatenate the contents of the files, well, the savings come from gzip compression. When you have more characters in the file, there's more chance that some will repeat which means they will compress better. That's one. And then, the compression itself has an overhead which you incur once for the whole bundle of files, as opposed to for every file.
Now - let's make decoration images into sprites. Before:
... 15 image requests, 6.8K
After: (1 sprited image)
![]()
Result size: 1.4K, 7 times smaller!
Here the savings are so dramatic partially because the source files are GIFs and the result is a PNG8 but that's a whole other post.
So in conclusion: file concatenation is just awesome. You save both: bytes to download and, much more importantly, HTTP requests. Less of the green stuff in the waterfall!
x-type component concatenation
So far we combined .js with .js, css with css and images with images. How about a cross-component type concatenation?
You can inline images inside HTML and CSS (and why not JS if you want) using data URIs (another post coming).
And you can inline CSS and JS inside HTML too.
This means you can have your whole application inside of just one HTML file if you wish. Inside of the HTML you have inline styles, scripts and images.
Combining CSS with JS
Now how about mixing CSS and JS into one component. You can do that, and it's especially suitable for lazy-loaded, widget-type functionality.
Say you've loaded the page, then the user clicks some rarely used button. You haven't downloaded that content that is supposed to wow the user upon click of the button. So you send a request to grab it. The new content may come in the form of a JSON string. And what if the new content requires some stylesheet that was not part of the base page? You'll have to make another request to download that stylesheet too.
Or, you can download both content and styles in the same JSON response. You simply inline the style information as a string in the JSON. So:
1. uses clicks, you request feature.js which goes like:
{"content":"<p class=\"wow\">I'm a feature</p>", "style": "wow{font-size: 60px}"}
2. You process the JSON and shove the content into the page
var o = JSON.parse(xhr.responseText); $('result').innerHTML = o.content;
3. You add the styles to the head:
var wow = document.createElement('style'); wow.type = "text/css"; if (wow.textContent) { // FF, Safari wow.textContent = o.style; } else { wow.styleSheet.cssText = o.style; // FF, IE } document.documentElement.firstChild.appendChild(wow);
Nice and simple. Makes the features (that progressively enhance the page) atomic and self-contained.
More to reducing components?
For more and creative ways to reduce HTTP components you can take a look at MXHR and Comet
Another thing to check is the Keep-Alive setting on your server. Remember how there were 4 steps in the component download. When you request a second component you can have the connection open so that you don't need to re-establish it (skipping step 2). And since the DNS lookup was already made you get rid of step 1. Skipping 2 out of 4 is not bad at all.
Summary
Reducing the number of page of components is the top priority of any web performance optimization effort. HTTP requests are costly. In part because of the headers size overhead, but mostly because of the connection overhead. The browser spends a disturbing amount of time not downloading stuff, and we can't allow this!
This entry was posted on Saturday, December 5th, 2009 and is filed under CSS, JavaScript, images, performance. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
Get notification for future posts: follow me on Twitter or subscribe to my RSS feed

December 5th, 2009 at 9:17 am
Thanks for the summary. I already implement css and jss minification+concatenation on deployment, but I never heard about mixing js and css. Seems to be simple, I will make some tests.
December 5th, 2009 at 9:53 am
This is a very good article overall. Certainly, it’s becoming more and more important to reduce not only the amount of unnecessary content sent to pages (at least at initial page-load), but also optimize how the necessary stuff is transferred. Far too long, those things were only cared about by the jumbo major traffic sites, but now we know all of us have that responsibility, for the betterment of user-experience.
However, I still have to take slight issue with the concept of combining as much content as possible into a single stream of data. Yes, we need to reduce unnecessary HTTP requests and overhead — I just saw yesterday a profile of a page-load from a major online kids toy retailer that had over 60 different http requests, which is of course RIDICULOUS! But I just don’t think that “1″ is the ultimate best goal. I think there needs to be a balance between reducing HTTP requests and:
1. parallel byte download streams (for instance, loading JavaScript in parallel with LABjs) — if you take one 100k file and download it as two 50k files, it’ll download (in theory) in half the time, but then factoring in HTTP request overhead, it might go to 2/3 or 3/4 the time. But the point is, for these larger resources, it’s still likely to download faster in parallel than the single stream would have come down serially.
2. caching: not just caching during a single visit, but also caching across visits (say over a week, or even a month). Yes, I know that the browser cache is not reliable, and a non-trivial amount of people come with no primed cache at all. BUT, that doesn’t mean, IMHO, that the cache is moot — it’s still a very important piece of the puzzle.
For instance, if you have 4 JS files you concat into 1, and it’s now 100k in size… and then you realize that a few lines of that JavaScript are likely to be changed more often (say because you are tweaking UX/animations, or fixing bugs in new code, etc), if you change even one single byte of that combined file, then every single visitor will have to redownload the other 99.999k unnecessarily the next time they visit.
So, maybe we should profile and keep our JS in 2 files (or perhaps 3), based on factors like likelihood to change (for instance, frameworks and plugins *rarely* change, but the code which uses them is more likely to change as you tweak your site). You may pay slight costs in extra overhead on first page views, but over the life of your site and your visitors re-visits to your site, you will probably see better overall page-load performance. This may not be most important, but it’s not something to be completely forgotten either.
I’ve written up a bunch of thoughts in more detail on this topic over on this post, if you are interested in digging more into detail on concat’ing resources:
http://blog.getify.com/2009/11/labjs-why-not-just-concat
December 5th, 2009 at 10:31 am
I quite agree with Kyle. But I still have some concerns about combining js files. Of course, if we can combine all js files into 1 big file, it will improve performance, but it will also hurt the browser’s cache. For example, the first page contains 1.js, 2.js and 3.js, and combine them to all.js. So the browswer will get all.js and cache it. But for the second page which may only contain 1.js and 2.js, browser will need to fetch them again.
So it is not quite easy for us to find which files should be combine together. We need to balance between two perspectives:
1) most common files across all pages
2) combine as more files as possible
What’s your best practice about this issue? How to find the best strategy to combine js and css files?
January 25th, 2010 at 8:28 am
I’m trying to merge multiple file.js in a single file but some script don’t run.
I haven’t problems when I include js with
I did a file with inline script like this:
content of file1
content of file1
content of file1
content of file1
but the problems remain…
Could you please help me to understand why?
February 8th, 2010 at 11:57 am
Good post, many thanks.
I’ve been optimising for the past month and still trying to figure which are the best pay-offs’. It’s good to know I’m not the only optimising perfectionist…