CSS and the critical path

June 5th, 2012. Tagged: CSS, performance

Back when I was still actively into speaking at public events (way, way back, something like year and a half ago (which strangely roughly coincides with the time I joined Facebook, hmmm (hmm? (huh? what's with the parentheses? sure all of them are closed at this point?)))) I remember showing this slide:

The reason I'm bringing it up now is this experiment I saw today by Scott Jehl.

media="nonsense"

Scott added LINK elements with non-applicable media attribs, such as tv, too much min-width and pixel ratio of 6 among others:

<link href="inc/tv.css" rel="stylesheet" media="tv">
<link href="inc/min-width-4000px.css" rel="stylesheet" media="(min-width: 4000px)">
<link href="inc/min-device-pixel-ratio-6.css" rel="stylesheet" 
    media="(min-device-pixel-ratio: 6)">

And just for the fun of it, why not a nonsense value:

<link href="inc/nonsense.css" rel="stylesheet" media="nonsense">

Scott observed that (with one happy Opera nonsense exception) all browsers will load all this junk, all this CSS that they don't need.

(BTW, Opera 11.64 loaded nonsense css for me too)

Blocking rendering?

Having recently remembered how browsers block rendering because of print stylesheets, I speculated that all the nonsense media will also block rendering. Unfortunately I was right.

So not only browsers download useless bytes, but they also block the rendering of the page (or block window.onload, or both) until all the crap is downloaded. And by blocked rendering I mean showing a white page of death. Most browsers wait until all CSS is loaded because they don't like doing extra layouts and painting (except Opera).

Here's a test page for you to try:

http://www.phpied.com/files/css-loading/mq.php?mq=all

Change all with your media query of choice, hit Enter and weep.

E.g.
http://www.phpied.com/files/css-loading/mq.php?mq=tv
http://www.phpied.com/files/css-loading/mq.php?mq=nonsense

This test page loads css with delay: css1 delayed 5 seconds and css2 delayed 10 seconds. The HTML is:

<link rel="stylesheet" href="css1.css.php" type="text/css" media="screen" />
<link rel="stylesheet" href="css2.css.php" type="text/css" 
    media="<?php echo $YOUR_MEDIA_QUERY; ?>" />

The correct browser behavior should be:
1. load only the CSS you need
2. render
3. fire onload

Maybe even:
0. render if step 1. takes too long

Instead, randomness ensues: Firefox treats us to a white page for 10 seconds while downloading nonsense. Chrome takes 15 seconds to fire onload. (see the print CSS post for more)

So what are we to do? First, understand...

The evil that CSS do

  1. Browsers (except Opera) block rendering until all screen CSS arrives. With the worst possible experience: white page.
  2. Browsers download CSS they don't need, e.g. print, tv, device-ratio... And most browsers (except Opera and Webkit) block rendering because of these too
  3. Sometimes CSS blocks the other downloads too (not just block rendering, but block images and scripts that follow):

The critical path

When building high-performance pages we want to stay off the critical path. Critical is the path from the user following a link to the first impression and then the working experience. That's why we load javascript asynchronously and so on.

But I argue that CSS is not only on the critical path, it is the critical path. And because it's a jungle (network, 3g, edge) out there, anything on the critical path will fail. Guaranteed.

Think about this: you have an HTML page and then you have components. Without the HTML, there is no path really. Game over. Without images? Depends on the page, but you can live without images most of the time. Without JavaScript? Well you should build the pages so the important stuff, links, forms, content works without javascript. Without webfonts? You're kidding me, I don't need no stinkin' fonts when I'm late and running to the airport and checking in for the flight on the damn phone with the spotty mobile network while Wifi wants to connect and I have to say no, because if I say yes I'll wait for another page where I have to say "I accept" and aim at a miniscule checkbox with these sweaty fat fingers or worse I have to enter usernameandpassword, and omg-omg-OMG mobile.southwest.com wants to look like native iPhone and won't let me click until mountains of JS arrive, so no, don't talk to me about no damn fonts!

What's left on the critical path is CSS. Not only the page is ugly without CSS, we can live with that, but there is no page without CSS because the browser waits and waits and takes forever to timeout showing us a blank white page.

Get the CSS out of the way

So if you worry about performance, you should get the CSS out of the way as soon as possible. Get off the critical path. Make CSS small, minify, compress, load from the same hostname even (no DNS) and inline, if small enough. Yup, inline.

Take a look at these highly optimized experiences...

Look ma, no CSS!

Yes, these pages make no CSS requests whatsoever.

If your CSS is not puny enough to be all inline (Guy has some observations on what puny means) it should at least be a single file, way at the top of the document, with the first flush. Just get it over with. Your users will love you and praise you and use words like smooth and snappy.

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

41 Responses

  1. If you open a web page, load it, then lose internet connectivity, you still want to be able to print it based on the print stylesheet (if any).

    The same goes for most of these media queries. What if you load a page on your laptop, then lose internet connectivity while connecting it to a huge 9001px wide television screen? You’d still want the (min-width: 4000px) stylesheet to be applied.

    I’m afraid browsers have no choice but to load these resources, even if they aren’t used right away. I agree they could/should defer these downloads until after the load event, though.

  2. Interesting. Thanks for the detailed explanation.

    I use this technique for my homepage and you can tell the speed of rendering, under 1 sec even it’s a shared hosting.

    In the other hand, the advantage of placing the CSS in a separate file is that the browser will cache it, and it will be instantly there for next requests. No need to download CSS code in every single request. So it’s important to keep a balance, and most important thing is to minify your CSS as you said.

    I’m just wondering if @import css is also blocking.

    Anyway, @import it’s not considered a high-performance best practice (Souders has an article “don’t use @import” where it is explained). But if you could add that to your test will be great!
    Thanks

  3. Agree with you Stoyan, I generally inline all CSS anywhere. If CSS is compressed down to a few KB, adding it to the page itself usually doesn’t slow down page loading (much). And the benefits regardings robustness and the earlier rendering definitely offset the costs.

  4. @Mathias – The case you describe (lose of Internet connectivity after the onload but before printing, changing screens, etc) is relatively rare. I do not think that pages must download these currently irrelevant CSS files by default just in case, even after onload.
    In 99% of the cases, that’s wasted data being downloaded for nothing. It should be a user preference turned off by default.

  5. Jason McGovern

    I don’t agree with inline styles unless we’re talking about a single page site.

  6. Ryan Romanosky

    Amusingly enough, developers seem to be moving in the opposite direction (i.e., relying more on what some could call frivolous CSS). While I don’t think all CSS should be inline, I do think that nonsense CSS shouldn’t be loaded unless needed. However, I think that should be the browsers’ job and not the coders’. That’s not to say people shouldn’t streamline their CSS (they should) but having everything inline is asking for a bit much on heavily-animated sites that don’t use Flash and don’t want to deal with Javascript. I still support your argument though, because I think it can/will spur-on the advancement of CSS loading and placement to help minimize page loading instead.

  7. @Corbacho … you can use media queries to import, e.g.

    @import url(color.css) screen and (color);

    as showed in W3C http://www.w3.org/TR/css3-mediaqueries/

  8. From my view, inline CSS is only really cool if you’re dealing with relatively simplistic pages (like Google) or building a web application (where you move to the next page via AJAX, so CSS is only needed once).

  9. [...] CSS and the critical path / Stoyan's phpied.com. [...]

  10. I’ve spoken for years about making JavaScript async, but Stoyan is absolutely right – CSS has an even worse impact on performance. And yet, there’s been much less research and best practices around avoiding these CSS issues. I’m excited that Scott Jehl has devoted time to this topic with his eCSSential project. Another problem to tackle!

  11. Hi Stoyan, great writeup!

    I just ran a few tests with Chrome, and I think there are a couple of small but important adjustments that need to be made to: “Browsers block rendering until all CSS arrives”. Turns out, that’s not entirely true.

    WebKit’s PreloadScanner, which should be consistent across all webkit engines, specifically looks for media=”screen” stylesheets and kicks off their download in parallel (subject to other host limits, etc). Similarly, the first paint occurs when all “screen” stylesheets have been downloaded. So, in the example you shared above with “nonsense” stylesheet… if the “screen” stylesheet takes 5 seconds, then the first paint will occur at ~5s, and after that the browser will load the “nonsense” stylesheet (let’s say another ~5s). So, the “onload” time here is 10s, but the first paint time is ~5s.

    In other words: “Browsers block rendering until all media=’screen’ CSS arrives”. At least for Chrome (verified), but I’m guessing you’ll see same behavior across all other Webkit browsers (wouldn’t be surprised to see same behavior in FF, Opera, etc).

    P.S. Here’s the code in question: http://code.google.com/searchframe#OAMlx_jo-ck/src/third_party/WebKit/Source/WebCore/html/parser/HTMLPreloadScanner.cpp&exact_package=chromium&l=104

  12. You may want to try a trick I’ve used before – it sounds crazy, but hear me out. You put the CSS in-line in the head of the document on the first page (the likely entry point for a new user), so that there are no extra requests during loading of the page – then you simply put a link tag at the very bottom of the page linking to the file that CSS came from. Every other page just has the link tag in the head. If you’re worried about that inline CSS loading every time any person goes to that page, whether they have it cached or not, just set a simple flag in a cookie, then either dynamically change the page, or cache two separate versions (one with inline, one without) that you spit out based on that cookied flag. Works like a charm, and as long as the inline CSS and the CSS in the file are the same, it causes no rendering hiccups. You get the benefits of fast rendering without waiting on a separate request, and also the benefits of having that content cached for other pages. The down side is that you transfer that data twice, but since the second time is at the very end of rendering, it’s not that big a deal (unless you have a huge stylesheet, but if that’s the case, you have bigger problems than this…).

    Here’s a thought: what if, when a browser sees a link (or script) tag with both content and an href (or src) attribute, it were to simply cache the content string as the contents of the references file in the cache. Haven’t thought that through completely – obviously it’s odd since style tags are used for inline css while link tags are used for external – that’s always seemed dumb to me. It’s also seemingly dumb that content of a script tag simply gets ignored if it has a src attribute… So there’s my crazy thought of the day – enjoy!

  13. Have you ever considered writing an e-book or guest authoring on other blogs? I have a blog based upon on the same information you discuss and would really like to have you share some stories/information. I know my readers would value your work. If you are even remotely interested, feel free to shoot me an e-mail.

  14. I would suppose the reason for all the browsers downloading every single stylesheet no matter if it fits or not has to do with their inner architecture/preparser. Because it totally reminds me of the situation over at the responsive images brouhaha playground where the WHATWG/browser makers ditched the W3C community’s idea of enriching images with MQ switches because of severe implementation obstacles. Reason being that the preparser/resource scanner doesn’t know nothing about MQ, and cannot be taught either.

  15. I’m curious is the average amount for “and inline, if small enough”?
    For example at http://www.lacne-pc.sk/ I have 15K CSS and bunch of from FB likebox (http://i.imgur.com/mQobO.png) which I will suggest to remove when I talk with the client.
    The minify result for that CSS Input: 16.609KB, Output:13.246KB @ http://www.codebeautifier.com/ (with highest compression)

  16. Ilya Grigorik,

    Thanks for looking into the WebKit behavior. It’s good to know that at least only screen media blocks in many cases.

    That said (and I apologize that my demo was probably misleading on this), the problem as I’ve encountered it is actually mostly with “screen” stylesheets. Most of the overhead in a responsive design is in CSS intended for unused screen @media breakpoints intended for different viewport ranges.

    If this problem doesn’t seem significant, consider a large-scale responsive site like the BostonGlobe.com. The CSS driving most layouts on that site is concatenated into a single file, both to save on HTTP requests, and also because of the problem described in this article (users would get worse performance if stylesheets were split into media-specific links). I’d estimate that on that site, a small device is not using at least 60% of the CSS it downloads, since the CSS for each breakpoint up to desktop resolutions is all downloaded. And of course, that’s almost entirely screen-based media, so according to your findings, it’s all going to block page rendering.

    If browsers were smarter about deferring the load of inapplicable stylesheets until either a) the page has begun rendering, or b) the browser conditions match that query and the stylesheet is lazily requested, I think it would follow through on the perceived benefits of using CSS3 media queries. I don’t think authors would expect that non-applicable links end up blocking, let alone downloading at all, in environments where they aren’t needed.

    Since you mentioned that only screen media ends up blocking, I’m guessing we could imply that the browser is capable of evaluating any media type or query in making that decision. If so, it seems to me that a large percentage of the sorts of media queries that are used in responsive designs (viewport and device width, and screen density) could be evaluated at that time and streamline page load.

    Looking forward to hearing your further thoughts on the matter!

  17. Scott, Stoyan, put together a continuation to the discussion: http://bit.ly/LccM9M

    Modern browsers are pretty smart about CSS.

  18. [...] CSS and the critical path / Stoyan’s phpied.com [...]

  19. [...] dragging down your page load time. Stoyan Stefanov has done excellent research into the effects of CSS on the critical path. He shows that browsers will block rendering (showing the user a white page of death) until all [...]

  20. I adore coupons! Coach will be the very best!

  21. What about the defer attribute?

    defer=”defer”

    To make it cross-browser, it’s not a big deal to make a JS shim/shiv for it on IE oldies

  22. [...] is one of performance’s worst enemies, as outlined by Stoyan Stefanov, because of this rendering blockage. It’s also worth noting that a browser will [...]

  23. [...] das jemand letztes Jahr nicht mitbekommen haben sollte, dies & dies sind die Gründe, weshalb man alle media styles in ein stylesheet packen sollte. Auch [...]

  24. [...] 因为有这种渲染阻塞阶段,CSS是性能最坏的敌人之一,正如Stoyan Stefanov阐述的那样 。而且也很有必要注意到浏览器在它开始渲染页面之前将下载所有的CSS。这意味着即使浏览器仅仅在屏幕上渲染页面,也要请求print.css。任何只是 基于一种媒体查询的样式表(如<link rel=”stylesheet” media=”screen and (min-device-width: 800px)” href=”desktop.css”>)都将会被下载,即使并不需要它们。 [...]

  25. [...] 因为有这种渲染阻塞阶段,CSS是性能最坏的敌人之一,正如Stoyan Stefanov阐述的那样 。而且也很有必要注意到浏览器在它开始渲染页面之前将下载所有的CSS。这意味着即使浏览器仅仅在屏幕上渲染页面,也要请求print.css。任何只是基于一种媒体查询的样式表(如<link rel=”stylesheet” media=”screen and (min-device-width: 800px)” href=”desktop.css”>)都将会被下载,即使并不需要它们。 [...]

  26. [...] 因为有这种渲染阻塞阶段,CSS是性能最坏的敌人之一,正如Stoyan Stefanov阐述的那样 。而且也很有必要注意到浏览器在它开始渲染页面之前将下载所有的CSS。这意味着即使浏览器仅仅在屏幕上渲染页面,也要请求print.css。任何只是基于一种媒体查询的样式表(如<link rel=”stylesheet” media=”screen and (min-device-width: 800px)” href=”desktop.css”>)都将会被下载,即使并不需要它们。 [...]

  27. [...] 因为有这种渲染阻塞阶段,CSS是性能最坏的敌人之一,正如Stoyan Stefanov阐述的那样 。而且也很有必要注意到浏览器在它开始渲染页面之前将下载所有的CSS。这意味着即使浏览器仅仅在屏幕上渲染页面,也要请求print.css。任何只是基于一种媒体查询的样式表(如<link rel=”stylesheet” media=”screen and (min-device-width: 800px)” href=”desktop.css”>)都将会被下载,即使并不需要它们。 [...]

  28. [...] 因为有这种渲染阻塞阶段,CSS是性能最坏的敌人之一,正如Stoyan Stefanov阐述的那样 。而且也很有必要注意到浏览器在它开始渲染页面之前将下载所有的CSS。这意味着即使浏览器仅仅在屏幕上渲染页面,也要请求print.css。任何只是 基于一种媒体查询的样式表(如<link rel="stylesheet" media="screen and (min-device-width: 800px)" href="desktop.css">)都将会被下载,即使并不需要它们。 [...]

  29. [...] 因为有这种渲染阻塞阶段,CSS是性能最坏的敌人之一,正如Stoyan Stefanov阐述的那样 。而且也很有必要注意到浏览器在它开始渲染页面之前将下载所有的CSS。这意味着即使浏览器仅仅在屏幕上渲染页面,也要请求print.css。任何只是基于一种媒体查询的样式表(如<link rel=”stylesheet” media=”screen and (min-device-width: 800px)” href=”desktop.css”>)都将会被下载,即使并不需要它们。 [...]

  30. [...] один из самых страшных врагов быстродействия, подчеркивает Стоян Стефанов, — именно из-за этой блокировки отрисовки. Также [...]

  31. [...] 因為有這種渲染阻塞階段,CSS是性能最壞的敵人之一,正如Stoyan Stefanov闡述的那樣 。而且也很有必要注意到瀏覽器在它開始渲染頁面之前將下載所有的CSS。這意味著即使瀏覽器僅僅在屏幕上渲染頁面,也要請求print.css。任何只是基於一種媒體查詢的樣式表(如<link rel="stylesheet" media="screen and (min-device-width: 800px)" href="desktop.css">)都將會被下載,即使並不需要它們。 [...]

  32. [...] один из самых страшных врагов быстродействия, подчеркивает Стоян Стефанов, — именно из-за этой блокировки отрисовки. Также [...]

  33. [...] 因为有这种渲染阻塞阶段,CSS是性能最坏的敌人之一,正如Stoyan Stefanov阐述的那样 。而且也很有必要注意到浏览器在它开始渲染页面之前将下载所有的CSS。这意味着即使浏览器仅仅在屏幕上渲染页面,也要请求print.css。任何只是 基于一种媒体查询的样式表(如<link rel="stylesheet" media="screen and (min-device-width: 800px)" href="desktop.css">)都将会被下载,即使并不需要它们。 [...]

  34. [...] будет скачиваться такое, какое нужно. Страница не будет отрисовываться, пока файл стилей со шрифтом не [...]

  35. [...] outlined by Stoyan Stefanov CSS is one of the performance worst enemies because it blocks the site from rendering. The Browser will block your page from rendering until [...]

  36. […] CSS and the Critical Path […]

  37. オメガ シーマスター コーアクシャル ニクソン 腕時計 人気 http://www.do02.com/

  38. […] is one of performance’s worst enemies, as outlined by Stoyan Stefanov, because of this rendering blockage. It’s also worth noting that a browser will download all CSS […]

  39. Thanks designed for sharing such a pleasant idea, paragraph is fastidious,
    thats why i have read it entirely

  40. […] 因为有这种渲染阻塞阶段,CSS是性能最坏的敌人之一,正如Stoyan Stefanov阐述的那样 。而且也很有必要注意到浏览器在它开始渲染页面之前将下载所有的CSS。这意味着即使浏览器仅仅在屏幕上渲染页面,也要请求print.css。任何只是 基于一种媒体查询的样式表(如<link rel=”stylesheet” media=”screen and (min-device-width: 800px)” href=”desktop.css”>)都将会被下载,即使并不需要它们。 […]

  41. […] but, as you may have heard, CSS is the worst type of component and needs to be done and over with as soon as possible. Otherwise your users stare at a blank […]

Leave a Reply