Search code examples
htmlcssgoogle-chromebrowserrendering

How does CSS blocks rendering?


I'm still unable to understand this concept of "CSS is render-blocking" very clearly. I do understand very well how JS is parser blocking. But, the former is still a little unclear to me.

Let's take an example:

index.html:

<!DOCTYPE html>
<html>
<head>
  <title>Some Document</title>
  <link href='cdn1.com/styles1.css' rel="stylesheet"/>
  <link href='cdn2.com/styles2.css' rel="stylesheet"/>
</head>
<body>
   ...
   ...
</body>
</html>

style1.css (from cdn1):

body { background: blue }

style2.css (from cdn2):

body { background: red }

Now, let's assume style1.css from cdn1 took 1 sec to load whereas style2 from cdn2 took 500ms. I want to know what all things the end-user would see happening in the browser between the following timeline:

  1. at time T < 500ms: Would there be a FOUC or a blank page since CSS is render-blocking and render tree won't construct until we have the styles1.css file
  2. at time 500ms < T < 1sec: Would there be a red page (since style2.css has already loaded), a FOUC or still a blank page for the reason mentioned in point 1.

Also, would the result be consistent on every browser out there, primarily, Edge, Chrome, Firefox and Safari?


Solution

  • The following examples uses resources that are deliberately delayed to show what happens. Because it uses dynamic resources to show a different id each time and provide cache-busting, I can't use a stack snippet, but I'll show you the critical code and a link to a working example.

    In the first example we have the case you describe.

    <!DOCTYPE html>
    <html>
    <head>
      <title>Render Blocking Test - A</title>
      <link href='/css/styles1.css?delay=1000&x={cachebuster}' rel="stylesheet"/>
      <link href='/css/styles2.css?delay=5000&x={cachebuster}' rel="stylesheet"/>
    </head>
    <body>
       <h1>Render Blocking Test - A</h1>
       <b>{cachebuster}</b>
    </body>
    </html>
    

    The placeholder "{cachebuster}" is replaced by a different GUID each refresh. Delays for the two CSS resources are 1 second and 5 seconds to show the delays more clearly

    styles1.css contains

    body { background: blue }
    

    styles2.css contains

    body { background: red }
    

    See it working here: http://alohci.net/text/html/render-blocking-a.htm

    You'll see that the page is blank until the 5 seconds have passed, and all the css resources are downloaded. Or on refresh, the previous page remains shown until the 5 seconds have passed (the GUID changes after 5 seconds). The background, when it and the new content appears, is immediately red, not white or blue. This is what render blocking means - no new render of the page is done until the render-blocking resources are available.


    For comparison, see this second example.

    <!DOCTYPE html>
    <html>
    <head>
      <title>Render Blocking Test - B</title>
      <link href='/css/styles1.css?delay=1000&x={cachebuster}' rel="stylesheet"/>
    </head>
    <body>
       <h1>Render Blocking Test - B</h1>
       <b>{cachebuster}</b>
       <script src="/js/script1.js?delay=4000&x={cachebuster}"></script>
       <link href='/css/styles2.css?delay=5000&x={cachebuster}' rel="stylesheet"/>
    </body>
    </html>
    

    See it working here: http://alohci.net/text/html/render-blocking-b.htm

    What we've got here is a single render-blocking resource for one second, then some content - the heading and the GUID - then a script which takes 4 seconds to download and then a second render-blocking resource.

    In this case, the text and the blue background appear after 1 second. The script loading parser-blocks, so the second css resource is not yet parsed, and therefore it can't block the rendering. Consequently, the blue background is seen. Then the script loads, the second css resource is parsed, and render-blocks until it too is loaded, at which point the background changes from blue to red.

    Finally, you might notice that is only takes 5 seconds for the red background to appear, not 9 seconds as you might imagine. That because the parser can still look ahead when it's parser blocked, recognise that it'll probably need to download the second css resource and pro-actively fetch it, even though it can't use it until the parser unblocks.