Reducing the number of page components

December 5th, 2009. Tagged: CSS, images, JavaScript, performance

2010 update:
Lo, the Web Performance Advent Calendar hath moved

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:

  1. DNS lookup
  2. Initial connection
  3. TTFB (Time to first byte)
  4. 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?

  1. A significant chunk of time is spent in activities other than downloading.
  2. 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!

Tell your friends about this post: Facebook, Twitter, Google+

13 Responses

  1. 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.

  2. 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

  3. 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?

  4. 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?

  5. 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…

    :)

  6. [...] access optimizationby Stoyan Stefanov This blog series has sailed from the shores of networking, passed down waterfalls and reflows, and arrived in ECMAScriptland. Now, turns out [...]

  7. [...] the payload: compression, minification, 204sby Stoyan Stefanov After removing all the extra HTTP requests you possibly can from your waterfall, it’s time to make sure that those that [...]

  8. [...] points of having fewer componentsby Stoyan Stefanov Last night I talked about the benefits of reducing the number of page components and the resulting elimination of HTTP overhead. A little carried away into making the case [...]

  9. Well done, the site is much more pleasant to use.

  10. programs…

    [...]Reducing the number of page components / Stoyan’s phpied.com[...]…

  11. Less is better, especially in the web. Congrats for this article.

  12. Thanks a bunch for sharing this with all people you really recognise what you’re talking approximately! Bookmarked. Kindly additionally discuss with my website =). We can have a hyperlink alternate contract between us

  13. Just as relevant 4 years after posting. If not more so.
    These days there are more tools such as dataURLs and inline SVG in the web optimizers toolkit.

Leave a Reply