Rendering · stage 04
The critical path
Before a pixel can be drawn, the browser must build two trees, combine them, and know the rules. Anything in the way of that sequence stalls the screen.
Stage 04 of six/Rendering, piece 1 of 3/Long piece/Next: Reflow and repaint
What the browser needs before it can paint
The moment a document starts arriving, the HTML parser begins building the Document Object Model — the tree of elements that represents the page's structure. But the DOM alone is not enough to paint anything. The browser also needs the CSSOM: the CSS Object Model, a parallel tree that maps every selector to its resolved values. Only when both exist can the engine create the render tree, which is the DOM and CSSOM merged, with invisible nodes (anything with display: none) pruned out. The render tree is what layout operates on, and layout must finish before pixels can be composited onto the screen. That chain — parse HTML → parse CSS → build render tree → layout → paint — is the critical rendering path, and every resource on it is a potential bottleneck.
The HTML parser is unusually forgiving and incrementally progressive: it starts building the DOM immediately and can do useful work with a partial document. CSS is different. A stylesheet, once discovered, blocks rendering completely. The browser has no safe way to paint anything while a stylesheet is outstanding, because any element on the screen might be restyled by the rules yet to arrive. This is a deliberate architectural choice, not a bug. Without it, users would see a flash of unstyled content — raw HTML in the browser's default styles — before the authored rules slammed in. The block is the lesser evil, so it is baked into the model.
Discovering a stylesheet happens when the parser hits a <link rel="stylesheet"> element. At that point the browser makes a new network request and pauses the render pipeline until the response arrives and is parsed. If that stylesheet lives on a slow origin, or if the network is congested, the page stays blank. A stylesheet in the <head> that arrives in the first few round trips costs relatively little; one that arrives late, or that triggers a redirect, can hold the screen dark for a visible interval.
Scripts make it worse
JavaScript does not merely sit beside the critical path — it can block both parsing and rendering at once. When the HTML parser encounters a classic <script> element (no defer, no async), it stops, fetches and executes the script, then continues. The reason is conservative but correct: a script can call document.write, which inserts content into the stream, so the parser cannot safely look past the tag. Because an executing script might also query computed styles, the browser first ensures any pending stylesheets are fully parsed before running the script at all. The result is a chain: stylesheet blocks script, script blocks parser, parser not finishing blocks render.
The defer and async attributes break parts of this chain. A deferred script is fetched in parallel with parsing and executed only after the document is fully parsed; it preserves execution order for multiple scripts and never blocks rendering. An async script is also fetched in parallel, but executes immediately when it arrives, interrupting parsing if necessary. Neither attribute affects stylesheets — a stylesheet is render-blocking regardless, unless it is loaded conditionally in a way the browser recognises as not applicable to the current media.
Preload hints exist precisely to soften these cliffs. A <link rel="preload"> in the document head tells the browser to fetch a resource at high priority before the parser would otherwise discover it, without blocking on the result. The resource joins the cache and is waiting when the later tag arrives. The hint is surgical: it does not change execution order, only fetch timing.
Fonts: invisible text as a blocking mechanism
Web fonts introduce a subtler delay. When the render tree is built and layout runs, the browser knows which elements need which fonts. If a required font has not yet arrived, the browser has a choice: show nothing where the text should be (a flash of invisible text, or FOIT), or show fallback text in a system font and swap it out when the font loads (a flash of unstyled text, or FOUT). The CSS font-display descriptor controls which strategy a font face uses: swap produces FOUT; block produces a brief FOIT then swap; optional abandons the web font entirely if it does not arrive quickly enough.
Neither outcome is free. FOIT hides content from users on slow connections and is particularly harmful to readability and accessibility — text that renders invisibly for several seconds is text no one reads. FOUT causes layout shift when the replacement font has different metrics. The choice is a genuine trade-off, and the right answer depends on how much the font contributes to the design versus how fast the connection is likely to be. Font subsetting — serving only the character ranges the page actually uses — reduces file size and therefore the window during which either problem manifests. The W3C's CSS Fonts specification covers the font-display descriptor and its interaction with the cascade in detail.
The result is a chain: stylesheet blocks script, script blocks parser, parser not finishing blocks render.
Where the path narrows most
The narrowest point in the critical rendering path is nearly always in the <head> before the first content element. A stylesheet plus a synchronous script plus a web font, all arriving in sequence over a slow connection, can add multiple seconds to the time before any text appears. The browser's preload scanner — a lightweight lookahead that runs ahead of the main parser — mitigates this by spotting <link> and <script> tags early and issuing fetches before the main parser reaches them, but it cannot reorder what is fundamentally a sequential dependency.
HTTP/2 multiplexing helps because multiple resources can be in flight over a single connection simultaneously, eliminating the per-resource round-trip overhead that made waterfall diagrams on HTTP/1.1 so painful. But multiplexing does not eliminate the logical dependencies: the CSSOM still cannot be built until the stylesheet bytes arrive and are parsed, regardless of how efficiently those bytes were delivered.
Understanding the path means understanding why the order of elements in the <head> is not cosmetic. A stylesheet placed after a script that is itself placed after another stylesheet creates a dependency chain that no amount of server speed fully compensates for. The browser is trying to do the minimum safe work; the page's structure determines what that minimum is.
The defer and async attributes break parts of this chain.