Critical Rendering Path
The Five Stages
DOM construction — the HTML parser reads bytes and builds the Document Object Model tree.
CSSOM construction — the CSS parser reads every stylesheet and builds the CSS Object Model tree.
Render tree — the DOM and CSSOM are combined into a tree of only the visible nodes, each with its computed styles.
Layout (reflow) — the browser computes the exact size and position of every render-tree node on the page.
Paint — the browser fills in pixels: colors, text, images, borders, shadows, onto layers, then composites those layers to the screen.
critical-rendering-path.txt
HTML bytes ──► DOM
\
├──► Render Tree ──► Layout ──► Paint ──► Composite
/
CSS bytes ──► CSSOM1. DOM Construction
<script> tag, it must stop and run that script before continuing to build the DOM.2. CSSOM Construction
<link>, inline <style>, or @import—is parsed into the CSSOM: a tree of rules with their computed specificity and inheritance resolved.<head> blocks first paint completely, even if the DOM is fully parsed and ready.3. Render Tree
display: none are excluded entirely—they exist in the DOM but never enter the render tree (unlike visibility: hidden, which is included but invisible).4. Layout
5. Paint & Composite
transform and opacity animate without triggering a full repaint.How Render-Blocking Resources Delay First Paint
Two resource types can block the critical rendering path directly:
Resource | Why it blocks | Mitigation |
|---|---|---|
<script> (no async/defer) | Parser stops to download and execute it before building more DOM | Add defer/async, or move to end of <body> |
<link rel="stylesheet"> | Browser withholds paint until CSSOM is complete | Inline critical CSS, defer non-critical stylesheets |
Optimization Techniques
Minify HTML, CSS, and JS — fewer bytes to parse and download means every stage starts sooner.
Defer or async non-critical scripts — stop them from blocking DOM construction (see the async/defer page).
Inline critical CSS — put the small amount of CSS needed for above-the-fold content directly in a
<style>tag in<head>, so first paint doesn’t wait on an external stylesheet round-trip.Load remaining CSS asynchronously — for non-critical stylesheets, use a
<link rel="preload" as="style" onload="this.rel='stylesheet'">pattern so it doesn’t block first paint.Reduce CSSOM size — remove unused CSS; a smaller CSSOM is faster to construct and match against.
Avoid deeply nested selectors — simpler selectors are cheaper for the browser to match during style calculation.
critical-css-inline.html
<head>
<!-- Critical, above-the-fold styles inlined directly -->
<style>
body { margin: 0; font-family: system-ui, sans-serif; }
header { background: #111; color: #fff; padding: 1rem; }
</style>
<!-- Full stylesheet loaded without blocking first paint -->
<link
rel="preload"
href="/styles/main.css"
as="style"
onload="this.onload=null;this.rel='stylesheet'"
/>
<noscript><link rel="stylesheet" href="/styles/main.css" /></noscript>
</head>offsetHeight) right after writing one (like changing style.width) in a loop forces the browser to run layout synchronously and repeatedly—a pattern known as "layout thrashing." Batch reads and writes separately to avoid it.Quick Reference
Stage | Input | Output |
|---|---|---|
DOM construction | HTML bytes | DOM tree |
CSSOM construction | CSS bytes | CSSOM tree |
Render tree | DOM + CSSOM | Tree of visible, styled nodes |
Layout | Render tree + viewport size | Exact position/size per node |
Paint | Render tree + layout geometry | Pixels on screen |