The Critical Rendering Path: Why the Browser Cannot Paint Yet

  • vuetelemetry
  • Guides
  • 9 min de lecture

Every metric on your Lighthouse report sits somewhere on one chain: bytes to DOM, DOM plus CSSOM to render tree, render tree to layout, layout to paint. Optimising a late stage is wasted while an early one is stalled - and one stage blocks in a way almost nobody expects.

The critical rendering path is the sequence the browser runs between receiving the first byte of HTML and putting the first pixel on screen. It is worth knowing not as trivia but because it is a chain: each stage consumes the output of the one before it. That single property explains why most performance work misses - you can shave a stage that was never the one waiting.

Stage 0 - bytes arrive. Nothing in the path can start before the first byte does, which is why time to first byte sets a floor under everything downstream. No amount of front-end work lifts that floor; it is a server and network decision.

Stage 1 - HTML becomes the DOM. The parser reads the markup and builds a tree of nodes. This is incremental: the browser does not wait for the whole document, it builds as bytes arrive, which is what makes streaming HTML worth doing.

Stage 2 - CSS becomes the CSSOM. Stylesheets are parsed into a parallel tree of computed styles. Unlike the DOM, this one is not useful until it is complete. A later rule can override an earlier one, so a partial CSSOM could produce styles that are simply wrong. The browser therefore waits for every stylesheet it considers applicable before it will paint anything at all. That is what people mean when they say CSS is *render-blocking*.

Stage 3 - the render tree. DOM and CSSOM are combined into a tree of only what will actually be shown. Nodes with `display: none` do not appear in it, nor does anything in `<head>`. Note that `visibility: hidden` is different: the element is in the tree, occupying space, just not painted.

Stage 4 - layout. The browser computes the geometry of every node in the render tree: position and size, in real units, relative to the viewport. This is where a percentage becomes a number of pixels, and it is why an image without dimensions costs you later - the geometry has to be redone when the real size arrives, which is the mechanism behind cumulative layout shift.

Stage 5 - paint and composite. Painted pixels are produced, possibly on several layers, then composited into the frame you see. The first time this completes is your first contentful paint; the frame where the biggest element lands is your largest contentful paint.

The stage that blocks in a way nobody expects. A classic `<script>` stops the parser - that much is well known, and covered in render-blocking resources. The part that surprises people is the interaction between the two blocking mechanisms: a synchronous script may read computed styles, so the browser cannot run it until the CSSOM is ready. A stylesheet still in flight therefore delays a script that has already finished downloading, and that script is in turn holding the parser. One slow CSS file can stall DOM construction it has no obvious relationship with.

Why the browser is not as stuck as it looks. While the parser is blocked, a secondary *preload scanner* keeps reading ahead through the raw markup looking for resources to start fetching. It is why a blocked parser does not mean an idle network. It also has a blind spot worth knowing: it only sees resources written in the HTML. Anything a script injects later is invisible to it, and starts its download only once that script has run.

What this means in practice. Ask which stage is actually waiting before you optimise. Compressing images does nothing for a page whose paint is held by a stylesheet. Inlining critical CSS does nothing for a page whose first byte takes 800 ms. The chain has one slowest link at a time, and the profiler tells you which - the waterfall in your lab tool is a picture of this path, stage by stage.

And a caveat for single-page apps. Everything above describes the first paint. On a framework-rendered page the visible result may arrive well before the page is usable, because the markup still has to be wired up - see hydration for why a fast paint and a responsive page are not the same claim.

The practical takeaway is a habit rather than a checklist: before changing anything, name the stage you believe is the bottleneck, and say how you would see it move. If you cannot, you are optimising a link that was not the slow one. The Core Web Vitals guide maps each metric back to the stage it measures.

FAQ

What is the critical rendering path?

It is the sequence of steps a browser performs to turn HTML, CSS and JavaScript into pixels on screen: build the DOM from HTML, build the CSSOM from CSS, combine them into a render tree, compute layout geometry, then paint and composite. It is called critical because everything the user sees waits on it, and because the steps are strictly ordered - each consumes the output of the one before.

Why is CSS render-blocking but HTML is not?

The DOM can be built incrementally: a partial tree is still correct as far as it goes, so the browser makes progress as bytes arrive. The CSSOM cannot, because a rule that arrives later may override one that arrived earlier. Painting from a partial CSSOM would show styles the finished stylesheet contradicts, so the browser waits for the applicable stylesheets to finish before painting anything.

Does a stylesheet really delay a JavaScript file that has already downloaded?

Yes, if the script is synchronous. Such a script may read computed styles, so the browser cannot safely execute it until the CSSOM is ready. The script waits on the stylesheet, and the parser waits on the script. This is the least intuitive coupling in the path, and it means a slow CSS file can delay DOM construction that appears unrelated to it.

What is the preload scanner?

A secondary parser that reads ahead through the raw HTML while the main parser is blocked, starting downloads for the resources it finds. It is the reason a blocked parser does not leave the network idle. Its limitation is that it only sees what is written in the markup - resources added later by JavaScript are invisible to it and cannot start downloading until that script runs.

What is the difference between the render tree and the DOM?

The DOM is every node in the document, including things that will never be displayed - the contents of head, and elements hidden with display:none. The render tree contains only what will actually be painted, with its computed styles attached. An element set to visibility:hidden is a useful edge case: it stays in the render tree and still occupies its space, it simply is not painted.

How do I know which stage is my bottleneck?

Read a waterfall from a lab tool and look for what is waiting rather than what is large. A long gap before the first request points at the server, so TTFB. A first paint that lands well after the HTML finished points at a render-blocking stylesheet or script. Layout and paint costs show up as long tasks in the main-thread trace after the resources have arrived.

Stack liée