The Critical Rendering Path: How Browsers Draw Pixels
The critical rendering path is the sequence a browser follows from HTML bytes to painted pixels — DOM, CSSOM, render tree, layout, paint.
The critical rendering path is the sequence of steps a browser must complete before it can paint the first pixels of a page: parsing HTML into a DOM, parsing CSS into a CSSOM, combining them into a render tree, computing layout, and painting. Every millisecond spent in this pipeline delays what a visitor actually sees, which is why understanding it is the foundation of front-end performance work.
Step one: building the DOM
The browser reads HTML bytes and tokenizes them into a Document Object Model tree — a structured, in-memory representation of every element and its relationships. Parsing is mostly incremental: the browser can start building the DOM before the whole file has downloaded, which is one reason streaming HTML from the server beats waiting to send a complete document.
The catch is <script> tags. A plain <script src="..."> blocks HTML parsing until the script downloads and executes, because the script might use document.write to inject more markup. The async and defer attributes exist specifically to avoid this: defer downloads in parallel and runs after parsing finishes, async downloads in parallel and runs as soon as it’s ready, potentially interrupting parsing.
Step two: building the CSSOM
In parallel with DOM construction, the browser parses CSS — from <link> tags, <style> blocks, and inline style attributes — into the CSS Object Model, a tree of computed styles. Unlike HTML, CSS parsing is render-blocking by design: the browser can’t know the final appearance of an element until it has seen every rule that might apply to it, including ones later in the cascade that override earlier ones.
This is why CSS in the <head> should be minimal and why tools increasingly recommend inlining critical, above-the-fold styles and deferring the rest. It’s also why understanding the CSS cascade matters for performance, not just correctness — a stylesheet the browser has to re-evaluate repeatedly because of specificity conflicts adds real work to this stage.
Step three: the render tree
The browser combines the DOM and CSSOM into a render tree — a representation of only the elements that will actually be visible, with their computed styles attached. Elements with display: none are excluded entirely (though visibility: hidden elements are included, since they still take up space). This tree is what layout and paint actually operate on.
Step four: layout (reflow)
Layout, sometimes called reflow, walks the render tree and computes the exact position and size of every element in pixels, given the viewport dimensions. This is where a flexbox or grid container resolves how much space each child gets, where percentages resolve against their containing block, and where container queries evaluate the size of their nearest containment context.
Layout is expensive because it can cascade: changing the width of one element can force the browser to recompute the position of every sibling and descendant that follows it. JavaScript that reads a layout property (like offsetHeight) immediately after writing a style forces a synchronous recalculation — a pattern known as “layout thrashing” that’s one of the most common causes of jank in interactive pages.
Step five: paint and composite
Paint fills in the actual pixels — text, colors, borders, shadows — onto layers. Modern browsers then composite those layers together on the GPU, which is why properties like transform and opacity are cheap to animate: they can be handled entirely in the compositing step without triggering layout or paint at all. Animating width, top, or margin, by contrast, forces layout on every frame.
Why this maps directly to Core Web Vitals
Google’s Core Web Vitals are essentially instrumented checkpoints along this same pipeline. Largest Contentful Paint measures how long it takes the biggest above-the-fold element to reach the paint stage. Cumulative Layout Shift measures how much the layout step moves things around after the user has already started looking at the page. A slow critical rendering path shows up directly as a worse score on both.
Practical levers
| Stage | What slows it down | What speeds it up |
|---|---|---|
| DOM parsing | Large, deeply nested HTML | Streaming HTML, smaller documents |
| DOM parsing | Blocking <script> tags | defer/async, moving scripts out of <head> |
| CSSOM parsing | Large stylesheets, @import chains | Minimal critical CSS, deferred non-critical CSS |
| Layout | Complex selectors, forced reflows | Batching DOM reads/writes, content-visibility |
| Paint/composite | Animating layout-triggering properties | Animating transform/opacity only |
Frameworks that render on the server, like those using SSR instead of SSG, send a browser HTML that’s closer to its final form, which shortens the DOM-construction and layout work needed before first paint compared to a blank shell hydrated entirely by JavaScript. That tradeoff is also central to how hydration works — the browser still has to attach event listeners and reconcile interactivity after that first paint, which is a separate cost from the rendering path itself but competes with it for the main thread.
The takeaway
The critical rendering path is DOM plus CSSOM plus render tree plus layout plus paint, in that order, and a browser can’t skip a step. Performance work is mostly about shrinking or parallelizing each stage: streaming and deferring HTML and scripts, keeping render-blocking CSS small, avoiding forced synchronous layouts from JavaScript, and preferring compositor-only properties for animation. Once you can name which stage a slow page is stuck in, the fix usually follows.
Keep reading
Takina · · 4 min read What Is the Beacon API? navigator.sendBeacon()
The Beacon API lets a page send one last async request as it unloads, without blocking navigation or racing the browser's page teardown.
Takina · · 3 min read What Is Fetch Priority? The fetchpriority Attribute
fetchpriority lets you tell the browser which resources matter most, overriding its default heuristics to load critical assets sooner.
Takina · · 5 min read CSS will-change Explained: Compositing and Performance
The CSS will-change property hints the browser to prepare an element for an upcoming change, moving it to its own compositor layer. When to use it and when not to.