What's Critical Rendering Path?

Whenever we talk about web performance, we come across things like async/defer, perceived performance, and critical path rendering. In this blog, we're going to discuss in length about Critical Rendering Path and how does it affect web performance.

We can define CRP in one line as

  • The steps taken by the browser to convert HTML, CSS, and JavaScript into pixels, that can be painted on the browser, are called Critical Rendering Path(CRP).

The whole process involves several steps and processing of code. The following flow would give you an idea of it -

  • Whenever you hit some page on internet, browser goes to server and requests the page it needs.
  • Server replies with the data on the network. The data comes in form of bytes.
  • Once the data has come back CRP starts executing steps to process HTML files

🛠CRP steps -

1. Document Object Model(DOM) Construction -

  • Browser reads the HTML, CSS, and JS files and starts converting data to HTML markup. It uses a defined unicode type with an HTML page to convert data into characters.
  • After browser is done converting characters, it starts tokenizing the HTML page. Tokenizing is done to identify different tags and form nodes based on them. With tokenizing browser also converts the HTML element data into Objects because it needs to store information such as parentNode, childNodes, events attached to that respective element etcetera.
  • Once tagging is done and browser knows HTML elements then browser starts constructing nodes. Nodes store information about HTML elements.
  • After constructing nodes, browser starts constructing DOM and establishes a relationship between nodes as a parent, child, and siblings.
  • While DOM construction, if browser encounters external resources like JS, images then it is blocking request. Browser waits for request to resolve and then restarts DOM construction.
  • So depending on external resource fetching, DOM construction time varies.
  • We can avoid blocking resource by fetching non-critical data using async/defer keywords.

2. CSS Object Model -

  • Browser performs CSSOM construction after DOM is ready but CSSOM construction is render-blocking because browser waits for different CSS files to arrive.
  • This behaviour isn't strange. It exists for a reason.
  • CSS cascades down from parent elements. It means, say for example, styles given to body tag would go all the way down in DOM tree. But we might override the cascading styles from parents in child. So browser waits until it receives whole CSS and then constructs the CSSOM.
  • With custom styles given to any HTML page, there are set of pre-defined style that exists in browser. That is the reason, even if you write plain HTML files without styles. You would observe some basic styling done by browser. So CSSOM is constructed using both custom and pre-defined styles by browser.
  • Usually more specific rules increase the amount of work browser has to do.
  • For example, .inner {} selector would take less time than .outer .inner { } selector. Because once browser finds .inner { }, it has to go and find it's parent .outer { } also.

3. Render Tree -

  • Browser has DOM and CSS trees at this point. So browser knows what do we want to put on screen and how to style it but independently they don't mean anything.
  • So to construct render tree, browser would have to visit each node in DOM and find each nodes respective styling from CSSOM and finally construct a combination of both in a tree known as render tree.
  • So it combines DOM and CSSOM but it doesn't put every node from DOM in here.
  • Render Tree stores HTML elements/node that needs to be in view. So it doesn't have HTML elements like head, meta, script etcetera.
  • It also doesn't store elements that have property display: none or any of its descendants. Because render tree represents elements that are going to be painted on screen. So it omits any element that would not be part of our layout. We would talk in length about layout in the next step.

4. Layout -

  • Now browser has a render tree, that stores elements and their respective styles to be put in the browser screen. But during whole phase, browser didn't compute any position or size-related property. So how does the browser know what to put where? It needs some information so that it constructs the layout of HTML page.
  • That part is done here because based on viewport of device, these properties vary. This process is called layout or reflow.
  • Time taken by browser to do this layout depends directly on size of DOM tree because it has to perform render tree step again.
  • What causes layout/reflow in browser? So window resizing, device rotation, scroll etcetera are responsible to make browser do layout again. Since on all these events, size/position of elements would definitely change.
  • In this step, every relative unit, i.e. %, rem etcetera, is converted to specific pixels.
  • This GitHub gist shows what forces layout in browsers.

5. Paint -

  • With layout done, only part left is putting elements on screen in form of pixels.
  • Browser uses render tree to do this painting. Time taken by this step depends on size of DOM and amount of work required by browser to do for styling, layout, render tree construction.
  • We saw steps involved in converting data from bytes to pixels that the browser performs. That means the time taken by CRP is the initial loading time of our web app.
  • If we want to reduce the loading time of our web app then we need to optimize our CRP. Optimizing CRP lies in above steps. In order to optimize your CRP, you need to optimize each step and reduce the amount of time browser spends on each of them.

đŸ€”How do we optimize CRP?

  • Basically you need to reduce the time taken by each steps to optimize overall CRP but how would you do that?
  • If we go back for a refresher then we know that browser does the DOM construction and CSSOM, right?
  • Can we do something here? Yes. Let's discuss the solutions now.
  • To optimize DOM construction -

    • As we already saw, DOM is render-block. It is render blocking because if it encounters a link or a script tag, browser stops DOM construction waits and after the link is fetched or JS engine has completed running JS code in script tag, browser starts where it left the construction.
    • Browser does this because, when it encounters a script tag, it doesn't know what changes that script would do after completion. This is the same case even if you don't write JS code directly in script tag but write it in a different file and link via a script tag. Browser would still behave the same.
    • So in order to load app as soon as possible, we need to decide what our critical resources are and what aren't?
    • You can do this by attaching a async attribute on your script tag like this

      <script src="index.js" async></script>
      
    • When browser encounters async tag, it understands that this is not critical resource for page, it doesn't stop and keeps the DOM construction going to next part of file.

  • How can we optimize the CSSOM construction step?

🧐CSS perf optimizations -

  • As we saw, DOM and CSSOM construction are render-blocking and it affects CRP. We can't do anything for DOM cause to start painting pixels on browser we need elements but can we optimize CSSOM construction.
  • If we think our styles are composed of different things like we would have different set of styles for mobile devices and desktop devices.
  • At one point, we would use just one of them. So we can define and ask browser what to load and move to render tree(discussed in step 3) step instead of waiting for whole styles to arrive and load.
  • We can do this in several ways. Some of it is defining media types and media queries when we link our CSS files.
  • If you link CSS files like this
<link rel="stylesheet" href="index.css">

then it's CSS parse blocking.

  • If you know some parts of CSS would only be applied if the page loads on Mobile Devies or smaller screens. So you could defined them like this
<link rel="stylesheet" href="mobile.css" media="screen and (max-width: 680px)">
<link rel="stylesheet" href="portrait.css" media="orientation:portrait">

then this wouldn't be blocking since it only loads when screen size is under 680px.

  • In second link tag, this would only load when orientation would be portrait.
  • Putting animations on GPU instead putting heavy computations on main thread
  • Using property like will-change to let browser know beforehand that this property would change in the future. If browser encounters this property then it does some optimizations even before the element actually changes.

⚡If we want to put optimizations in points then -

  • Optimize your critical resources so that browser doesn't waste a lot of time fetching them.
  • Load critical resources as early as possible. Don't make your browser wait for them.
  • Understand what's important for first or initial load of your app and defer rest of resources by making them async and loading them later.

📚 Resources -

25