Md Toy Blog

The Browser's critical rendering path


When we write HTML CSS and Javascript we are glad to see a beautiful output. But in what order does the browser come up with the actual pixels to render on screen? This is important in order to understand how to optimized your Javascript code especially when using libraries such as React.

The browser takes any fetched page through what is called the critical rendering path. Here is how it looks like:

critical rendering path

The browser computes the render tree by combining the HTML tree (DOM) and CSS tree (CSSOM) along with executing any Javascript.

Paraphrasing Google:

  1. Construction of the DOM and CSSOM AND execution of Javascript. Javascript can only see the parts of the DOM and CSSOM tree that were constructed so far :

javascript is executed at the exact point where it is inserted in the document. When the HTML parser encounters a script tag, it pauses its process of constructing the DOM and yields control to the JavaScript engine; after the JavaScript engine finishes running, the browser then picks up where it left off and resumes DOM construction. Source Google.

  1. Render tree contains only the visible nodes required to render the page with inlined styles (so there is a reconciliation of CSSOM and DOM trees) into a single tree. (In the browser Dev tools it's captured within the Layout event, but conceptually they are separate tasks)
  2. Layout computes exact position and size of each object (taking the render tree as input, this step is also called reflow)

The output of the layout process is a "box model," which precisely captures the exact position and size of each element within the viewport: all of the relative measurements are converted to absolute pixels on the screen.

  1. Paint will translate this box model into actual pixels for display to the screen.

IMPORTANT NOTE: when javascript tries to access CSSOM before it has been constructed by the browser, Javascript execution will be paused until CSSOM is finished! IMPORTANT NOTE 2: inline and external javascript files will block the browser until finished. So fetching external files will make the browser wait even more. To circumvent this, use the async keyword to prevent the browser from blocking while it loads the external file. Read this. It will still block for execution though, that is why you are told to put the <script> tags at the bottom of the page.

So how does all this help us for React? Read this great article if you are wondering how to optimize using useMemo and useCallback.

TL;DR: useMemo returns a component that is only rendered when it's props change. But when passing a callback to a child memoized component, if the parent where the callback was created rerenders, the reference to the callback will change (a new function will be created).

So even if the actual callback contents have not changed, the memoized child component will (see the reference change) consider it as a prop change, and rerender. This makes our useMemo useless.

Solution: To prevent these undesired child rerenders, we can leverage the use of useCallback hook. It freezes the creation of new callbacks on parent rerenders. Callbacks will only be recreated when the list of dependencies passed as second props change. Successfully limiting the memoized child component rerenders.