Eliminating the CSS bottleneck

A big part of accelerating websites is eliminating bottlenecks. Scripts are likely the most discussed bottlenecks, but CSS files are equally bad. CSS files will block all subsequent downloads if there’s a script (internal or external) in between – which is the case on practically any real page.

Most sites are affected by this bottleneck. Even extremely fast sites like webpagetest.org could still benefit from losing this “stair” in the waterfall.

So what can you do about it? You can load CSS asynchronously. Here are the key highlights of what you’ll need to do to achieve that, while maintaining the same site functionality. Alternatively, you can just use Blaze, as we’ve been applying this optimization for a while now ;)

I’ll warn up-front that this isn’t trivial to do, but it’s definitely doable if your heart is set on it. Here’s the short list, details further down:

  1. Replace link & style tags with a JavaScript array
  2. Fetch the data for each CSS
  3. Apply CSS to the page
  4. Maintain the correct order
  5. Avoid FOUC by setting body visibility

Update: CSS files consistently block other resources on IE8 and often IE9, but not on Firefox and Opera. For WebKit, they only seem to block resources if they’re in the <head> of the document (likely a part of the preloader behavior). A cool and very simple hack found by Yoav Weiss is to add a div (or similar) element above the CSS links, which will make them not block. What it does is make WebKit (or at least Chrome) start the body earlier, and thus stop blocking. It’s super simple to do, just make sure you don’t create any side effects by ending the head early. Here are examples of CSS link in the head, in the body and in the head with Yoav’s trick.

1. Replace link & Style Tags with a JavaScript Array

First thing to do is to replace your existing link tags with a JavaScript array holding the different links. If your links use different media rules, you should remember that and store it in the array as well.

It’s important to keep this array in the right order, since you’ll want to apply the CSS rules in the right order. In fact, sometimes your inline styles intentionally attempt to override external stylesheets. If that’s the case, those styles need to be placed in the relevant spot in the array as well.

2. Fetch the Data for Each CSS

Once you have the array, you need to fetch the data for each CSS. This can be done by JavaScript in various way, for instance XMLHttpRequest calls (or jQuery.ajax()). Whatever technique you use, make sure you get notified when the data is available, so you can apply #3.

It’s important to make sure you submit the requests in parallel, and that you start the requests as early as possible in the page.

3. Apply CSS to the Page

Once data is made available, you can apply it to the page (see caveat in #4). Applying CSS rules to the page isn’t always as easy as it sounds. The primary technique is to dynamically create a “style” tag, and populate its text. In IE, you need to change the style tag’s “styleSheet.cssText” attribute. I couldn’t find a good reference on how to do it, but bits and pieces are scattered around the web, if you know of some, please post them in the comments.

4. Maintain the Correct Order

Note that you may receive CSS responses in a different order than the order they were requested, so you can’t simply apply the data as you receive it. Each time you get new data, you should first store it in the relevant place in the array. Then traverse the array from the top, apply any element you got data for, and shift that item out of the array. This way you know you’re applying the data in the right order.

5. Avoid FOUC by setting body visibility

CSS Link tags, beyond blocking resources, also block rendering. On most sites, that’s a desirable behaviour, since rendering the site without the styling makes it look (and perhaps be) broken. Seeing the unstyled page is referred to as FOUC – Flash of Unstyled Content.

To avoid FOUC, you can manipulate the body’s “visibility” style. The initial HTML can have an invisible body (e.g. ), and once all the files are loaded, set the visibility to true.

If you know the app more intimately, you can also choose to set the body as visible before ALL the CSS files are available. This is similar in notion to downloading JavaScript files but not executing them when running JavaScript asynchronously.

Avoiding @import statements

Another scenario in which CSS blocks other resources is when using @import statements to load CSS files. There’s an old but good description of the different cases here, which is mostly up-to-date. Generally speaking, you should avoid using @import statements, since they make browsers load files sequentially instead of in parallel.

When using async CSS loading, avoiding @import resources is even more important. Depending on how the dynamic CSS is applied, @import statements will keep files from being downloaded in parallel at best, or modify the CSS order at worst.

The Inevitable Tradeoffs

Almost all performance optimizations come with a slight price. In the case of Async CSS, there are two tradeoffs to consider:

  1. Making CSS async may slightly delay the CSS downloads themselves. Since more resources will be downloaded in parallel, the CSS files will need to compete with the others for bandwidth. This is true for any optimization that does more in parallel, and from our experience it’s a non-issue for CSS given their small file size (especially after minification and compression)
  2. CSS files are naturally a Single Point of Failure since rendering is blocked until all CSS files are downloaded. If a CSS download fails, browsers will still display the other content. When implementing async CSS, you can apply the same behaviour, and can in fact enforce your own timeout.

Summary

CSS loading is a significant bottleneck, and eliminating it can sometimes shave seconds of the load time. The CSS bottleneck also makes other un-blocking optimizations less useful. If the JavaScript at the top of your page is loaded asynchronously but the CSS files still block, images will still be delayed before they start getting fetched.

Making it not block isn’t trivial, but it’s a worthwhile effort.

Posted on October 6th, 2011
  • Anonymous

    Async CSS, might get you better page speed just by numbers. But the page will feel slower, just put scripts at the bottom to remove blocking.

    • Anonymous

      I agree that if you can move all of your scripts to the bottom of the page (or at least put none between your CSS links and the rest of the resources), that’s a better solution. 
      Problem is, that’s very hard to do in many sites, especially when building on existing platforms.

    • Yair Even Or

      css images will always get downloaded and loaded after the scripts, no matter if you put the CSS in the head and the scripts at the bottom

  • http://blog.yoav.ws Yoav Weiss

    IMO, the main advantage of async CSS is in avoiding “FrontEnd SPOF” (http://www.stevesouders.com/blog/2010/06/01/frontend-spof/).
    Modern browsers no longer avoid downloading other resources because of pending CSS download (so there is no waterfall “stair”), and delaying the CSS load will increase the time before a user can watch and access your site.

    • Anonymous

      I’m afraid modern browsers still block if there is a script after the CSS link. 
      Here’s an example page, with a simple link to a delayed CSS, followed by an inline script and then an image: http://www.blaze.io/experiments/test-css-async.htmThe script makes the browser, even Chrome, block the image until the CSS is complete. Here’s a sample of Chrome running this page: http://www.webpagetest.org/result/111006_75_335dec4a6851a5f619d16c9e6c140d54/While Async CSS won’t make the start render faster, it’ll make the complete page faster, and our experience shows that there’s no real negative impact on start render time. This is due to the small size of CSS files and the fact latency is what delays them, not bandwidth.

      • http://blog.yoav.ws Yoav Weiss

        I’ve answered here: http://blog.yoav.ws/2011/10/Unblocking-blocking-stylesheets
        Basically, you’re right, but a simple empty div tag before any stylesheet link causes all modern browsers (besides IE8) to avoid blocking in this scenario. Yes, it is an ugly hack. But it is simpler and better perf-wise than async CSS.

        • Anonymous

          Cool trick. Seems to make Chrome start the body earlier, and it looks like CSS links in the body don’t block resources. Blog updated.

  • http://twitter.com/CrabDude Adam Crabtree

    I’d be curious what affect this would have on creating at least an additional extraneous reflow. Does content reflow as it’s rendered for styled content? If so, this could save reflows, if not, this could add extraneous reflows. Affects would probably be marginal though…

  • raghu

    Can any1 show 1 sample example for the above steps.. As i’m new to this domain.. Thanks in advance