Optimizing the Critical Rendering Path

By Stephan Max

Recent years have seen two trends on the web:

  1. Web pages are getting bigger and bigger in terms of consumed bandwidth and loaded assets (CSS, JS, images, fonts).
  2. The share of users who surf the web with mobile phones and tablets is increasing.

Sure, at home you have your desktop computer with a really fast internet connection. Yet, a lot of people out there probably don’t visit your site with that same setup. Even a nice 4G connection on the road only solves the problem of available bandwidth. There still remain the problems of network overhead and mobile latency.

This has led to a whole new thinking about page speed and optimization. Nowadays, Google ranks pages higher if they render in less than a second. This is supposed to be an optimal response time, considering that 10ms is perceived as an instant reaction, 3s lets users drift off, and 10s is a reason to never visit your site again.

But how can you accomplish this ambitious goal without cutting off features or dramatically disfiguring your site?

The idea is not only to put your pages on a decent server and trim the site’s weight as much as possible, but to optimize the chain of events that take place to render your web site in the browser window. This chain is called the Critical Rendering Path.

From Page Loading to Page Rendering

Google defined the critical rendering path as the sequence of steps the browser goes through to turn “the code and resources required to render the initial view of a web page” into actual pixels on the screen.

This changes the notion of page speed completely. It is not enough to load a whole page as fast as possible. The crucial part is to deliver mobile users a fast visible response to guarantee a seamless experience. The term critical is key here, since not everything in your code is needed to produce something in the browser window.

In order to optimize the critical path and thus the page rendering speed, let’s have a look at those mentioned steps and how they interact with each other.

From Blank to Content

Imagine a user visits our (admittedly simple) web site. After entering the URL, the browser sends a request to the server and will eventually get the following HTML as a response.

<!DOCTYPE html>
<html lang="en">
    <title>Sample Site</title>
    <link href="style.css" rel="stylesheet">
      Hello <span>there</span> SitePoint!
      <img src="photo.jpg">

The browser is parsing this stream of code bytes into the Document Object Model, or DOM. The DOM is a full tree representation of the HTML markup:

The DOM tree structure for the code snippet above

Note: The browser builds up the DOM incrementally. That means it can start to parse the HTML as soon as the first chunks of the code come in and it will then add nodes to the tree structure as needed. This can be leveraged for some advanced but effective optimizations (which are out of the scope of this article).

At this point, the browser window is still blank. Note how a CSS file is referenced inside the <head> tag. The styling of a site is definitely critical for rendering and hence has to be downloaded as soon as the HTML parser reaches the <link> tag.

p { font-weight: bold; }
p span { display: none; }

This small CSS file is then parsed into the CSS Object Model, or CSSOM.

The CSSOM tree structure for the CSS two-liner above

Unfortunately, the CSSOM cannot be built incrementally like the DOM. Imagine if there was a third line in the stylesheet, for example p { font-weight: normal; }, which overrides the first p declaration. This demonstrates that we have to wait until all the CSS is downloaded and processed before we can move on with the rendering step. CSS is render blocking.

As soon as the browser has a machine-readable representation of both the markup and CSS, it can build the render tree. This structure combines DOM and CSSOM while only capturing visible elements:

The render tree combining both the DOM and CSSOM introduced earlier

Note how the span tag is not part of the render tree (another reason why CSS is render blocking; only CSS tells the browser ultimately what should be part of the render tree).

The render tree is a good representation of the content we will later see in the browser window. The actual pixels appear after the two remaining steps: Layout and paint. The layout step handles calculating position and dimension of every element with respect to the current viewport. Finally, the browser paints everything and displays the complete rendered page.

Every time the render tree is changed (for example by interactive JavaScript) or the viewport is changed (maybe by resizing the browser window or rotating your mobile device), the layout and paint step have to be rerun.

The complete critical rendering path for this simple example looks like this:

A graph illustrating the CRP for the HTML with an external CSS file

What about the image?

Actually, only HTML, CSS and JavaScript are considered critical resources (that is, assets that might block page rendering). It turns out, images neither block the DOM construction nor the initial rendering of the page. Have a look at the network timeline of our simple web site, provided by the Chrome’s DevTools:

The resources waterfall in the network timeline provided by Chrome DevTools

The blue vertical bar marks the DOMContentLoaded event. This event is fired as soon as the DOM is ready. The image is downloaded later, which proves that images don’t block parsing – or even rendering, for that matter. They do block the Load event (the red vertical line), though. This event signals that all resources the requested page needs have been downloaded and processed. That being said, image loading should of course be optimized. But images are not critical in terms of the rendering path.

Spicing Things Up with JavaScript

JavaScript is a powerful tool and has a huge impact on the critical path. Let’s expand our paragraph tag with an inline script:

  Hello <span>there</span>, SitePoint!
    document.write('How are you?');
    var color =; = 'red';
  <img src="photo.jpg">

Even this simple code snippet demonstrates that scripts can both query and change the DOM as well as the CSSOM. (For the sake of simplicity, assume that elem holds a reference to some particular HTML element on the page.) Since JavaScript can add things to the DOM (like the ‘How are you?’ text node), the parser has to stop its work until the script is fully executed. JavaScript is parser blocking.

The second JavaScript line above asks for the element’s color. Consequently, the CSSOM has to be present before the script can be run. CSS is therefore also script blocking.

Let’s see how the critical rendering path looks when we get rid of the inline script and replace it with a reference to an external JavaScript file:

<!DOCTYPE html>
<html lang="en">
    <title>Sample Site</title>
    <link href="style.css" rel="stylesheet">
      Hello <span>there</span> SitePoint!
      <img src="photo.jpg">
    <script src="app.js"></script>

A graph illustrating the critical path for the HTML with an external CSS and JS file

The external JavaScript file demands an additional request – the inline script would not have needed that. Also, don’t let the flow chart fool you: No matter which file, CSS or JS, arrives first in the browser, the CSS has to be parsed first. As soon as the CSSOM is present, the script content can be executed. Only after that, the DOM parser gets unblocked and can finish its work. Even our fairly simple web site example has some serious blocking and interfering going on under the hood.

Now that we know what steps are necessary to render a page and how the critical resources – HTML, CSS, and JavaScript – are intercepting each other, we can derive some optimization measures.

Three Steps to Optimizing the Critical Rendering Path

There are three spots where you can optimize the critical rendering path and thus the speed with which the browser produces a visible result for the user. Keep in mind that optimization is all about careful measurement and trade-offs. Unfortunately, there is never a right answer that fits all scenarios. Don’t hesitate to use help like the Chrome DevTools to see what’s going on.

1. Minimize the Bytes that Go Down the Network

This might be a no-brainer but you would be surprised how many web sites still don’t follow the easiest rule in speed optimization: The smaller your site, the faster it loads and renders. It is as easy as this!

Minify, compress, and cache your assets as well as your HTML. Yes, HTML is also render blocking. A lot of build tools, like Grunt or Gulp, come with minifying/uglifying plugins. These clear your code of whitespace, comments and more to prepare your files for production.

Compression decreases your page load even more while caching spares the browser network round trips to the server and back. Instead of requesting every critical resource at every page reload, it can use a cached, local copy.

Note: If you minify your HTML, you may risk content and styles not displaying correctly. For example, if you use natural white space to separate inline-block elements, that white space would disappear on minification. So use HTML minification carefully.

2. Minimize Render Blocking CSS

Our beloved styling language is an important part of every rendering path. Remember that CSS both blocks rendering and JS execution. It comes as no surprise that the golden rule for CSS is: Get CSS to the user as soon and as fast as possible! Make sure all the CSS link tags are in the head of your HTML code so the browser can dispatch the requests immediately.

Another strategy is to lower the amount of render blocking CSS with media queries. Say our sample web site has styles for printing and also special rules for devices in landscape mode. You could use this knowledge to split the CSS into several files and let the browser parse them conditionally.

<!DOCTYPE html>
<html lang="en">
    <title>Sample Site</title>
    <link href="style.css" rel="stylesheet">
    <link href="print.css" rel="stylesheet" media="print">
    <link href="landscape.css" rel="stylesheet" media="orientation:landscape">

The original style.css file should be significantly smaller now and is the only CSS resource that is always render blocking. The print stylesheet is only used for printing and not for displaying the page in the browser. Thus, it is not render blocking. The third CSS file has a dynamic media query and it is decided on page load and depending on the device’s orientation whether it is render blocking or not.

Apparently, we can use media queries to kind of outsource CSS code which is only needed under special circumstances or conditions. That decreases the file size of our critical main style file and consequently the time it takes the browser to parse it and continue with the rendering process.

Note: The browser will still download all the stylesheets, but this happens on lower priority and in parallel to the rendering process.

Last but not least you can also inline your render blocking CSS and put it directly into the HTML file. This spares the browser a network roundtrip and only leaves the HTML file (and maybe scripts) to be processed.

<!DOCTYPE html>
<html lang="en">
    <title>Sample Site</title>
      p { font-weight: bold; }
      p span { display: none; }
    <!-- No link tag needed! -->

As mentioned before, it is all about trade-offs. The trick above might buy you a decreased network latency, but potentially blows up your total load in case the critical CSS has to be inserted in many files.

There are tools to help you do this automatically too. Here are a few options:

3. Minimize Parser Blocking JavaScript

The same is true for JavaScript: If you need some lines of code for the initial page rendering, you might consider to inline them into your HTML file to save round trips over the network.

In an optimal case, you drop JavaScript all together for the initial rendering of the page. If that is not possible, try to defer the JavaScript execution and postpone it until the load event.

Also, check your scripts or third party plugins carefully. If they don’t interact with the DOM and CSSOM in any way, chances are good that you can load them asynchronously, or short async. Achieving this is easy:

    <script src="app.js" async></script>

By doing so, you just told the browser it is OK to not execute the script at the exact point where it is referenced in the HTML document. This would allow the browser to continue to build the DOM and execute the script when the DOM is ready. Imagine app.js only contains setup code for Google Analytics and several social media features. If the code leaves DOM and CSSOM untouched, it is a perfect candidate for being made async and thus unblock the parsing.


Much of the research on this piece was taken from a few different sources:

So be sure to check out those sources for more details on the stuff covered above.

For a summary of this article, here comes the TL;DR:

  • The notion of page speed has shifted from simple page loading to page rendering
  • The Critical Rendering Path comprises all steps to turn critical resources into a visible browser output: DOM and CSSOM, JavaScript, render tree, layout and paint phase
  • HTML is render blocking, but the DOM can be built incrementally
  • CSS is render and script blocking, treat it carefully and optimize it with inline styles or media queries
  • JS is parser blocking, use it sparingly during the initial page load, defer execution or try to load it asynchronously
  • Don’t forget that size still matters and minify, compress, cache

Don’t be afraid to fire up tools like the Chrome DevTools, PageSpeed Insights, or WebPagetest to find out what is going on before your web site gets displayed.

The page rendering speed of your web site is crucial for your visitors who typically don’t treat your pages with patience. Keep that in mind when you are tweaking your site’s performance to the fullest. It sure is rewarding!

  • Vahan Asryan

    It would be nice to have also something about fonts loading influence on web page rendering.

    • True that! Thanks for the suggestion, watch out for a follow-up article ;)

    • Bruno Seixas

      There´s some techniques related to web fonts, like storing then in Storage (LocalStorage).

    • Key thing to remember with webfonts is they block rendering in Safari, Chrome, Opera and Firefox – the last three wait 3 seconds before timing out and falling back to a default font, Safari waits longer.


      The browser doesn’t know which fonts it needs until the render tree is being built i.e. until the CSS styles are matched against the DOM, and only then does it request them – it’s one of the reasons fonts can be such a painful user experience on slow networks.

      We then get into the tradeoff of base64 encoding fonts which of course results in larger CSS, so longer download and later construction of the render tree, which is why people go down the route of storing them in localStorage.

      Ilya Grigorik put together a great idea for controlling font-blocking behaviour in more detail –

  • The thing I love about SitePoint articles, I usually come away with somehing I didn’t know! I love the hint about async. Thank you for a great article, Stephan.

    • Best compliment one can wish for, thank you Karl!

      • PauletteVSmith

        My last pay >>>>>>>

        {Go to next link in this site}

    • If you intend to use it, keep in mind that the “async” attribute on script tags isn’t
      compatible with IE9 and below. You would need to use an alternative
      method for asynchronous script loading for true cross-browser
      compatibility. Example follows:

      var customNodeName = document.createElement('script');
      customNodeName.type = 'text/javascript';
      customNodeName.async = true;
      customNodeName.src = 'path/to/js/file.js';

  • Albert Casademont

    But what about CSS caching? I mean, this CSS inlining example is nice because here there are 2 lines of CSS. What about rich, responsive sites? The inlined CSS could be quite big and we are losing the power of the cached assets.

    Assume we don’t inline anything, have an external link. Ok, this will block on the first visit but what about the N-following visits? The CSS is cached in the browser, no external request, no block at all (or absolutely minimal).

    So with the inlining maybe we are speeding up first-time visitors but we are heavily “overheading” the ones who could have that inlined CSS cached and not downloaded everytime they visit the page again. And tools like PageSpeed are pushing more and more towards this trend of “inline everything forget about caching”.

    I would love to push/inline the CSS for first time visitors and place an external sheet for the second and followinig visits, THAT would be awesome, but I’m afraid I can’t thing of a reliable mechanism for it to work properly.

    • Hi Albert, thanks for taking the time and entering the discussion!

      First of all, please remember that none of the suggestions in my article are set in stone. It is all about measuring, trade-offs and deciding what makes sense for your particular use case.

      I am aware that external stylesheets and scripts are kind of a consensus among developers, almost certainly because of common best practices, like the YSlow rules. However, inlining might come in handy for small files (where caching effect is pretty much negligible) and especially for all the above-the-fold stuff. I am not suggesting to inline your multi-hundred-kb styles, but rather all the lines that are crucial to render an initial view for your users.

      And there is more: Think about mobile where your network connection has to deal with packet loss and the TCP slow start, just to name a few obstacles (I can really recommend High Performance Browser Networking for the details). Maybe it makes sense to inline when you have an extra mobile site, where every server roundtrip matters.

      Or pages that change often or are only a gateway to actual content, like examples/demo pages or landing pages of that new startup you are doing.

      I guess what I’m saying is: Caching undoubtedly is and will be a mighty tool, but don’t rule out inlining completely!

    • Late reply here, but I just saw your comment (I’m the author of the linked Critical Path CSS generator, Penthouse).

      I’m not sure now whether this article makes this clear – but you absolutely want to cache CSS. What you inline helps kick of the first render more quickly, but for subsequent page loads you should rely on cached css. The easiest way of achieving this is by using a link tag, added just before the end of the body element (there are also more advanced ways of caching the css).

      As to how to avoid sending inline styles when you already have cached CSS loaded – there are some ways of achieving this. For example, you can set a cookie and check for that on every page load.

  • Alejandro Naso

    There is a particular situation with the images linked in the CSS, for example as background-image. They will be downloaded after the DOMContentLoaded event, because the net request is fired while discovered in the CSS. To improve it, try to refactor the CSS in order to get the minimum amount of backgroun-images or getting all in a sprite (only one net call), and prefetching that sprite

  • simon codrington

    Thanks for the article @stephanmax:disqus was a great read :)

    • Thanks, Simon, I am glad to hear that! I’ll also get that link fixed (well spotted, thanks!)

  • Great Article!

  • Thanks for sharing this @stephanmax:disqus. The visuals and example markup in the article we’re a very helpful, and to end the article you also gave a summary in bullet points – amazing man thanks!

Get the latest in Front-end, once a week, for free.