Frontend Performance/Foundations — From URL to Pixels
Lesson 2 of 20 · Episode 2

Critical Rendering Path

How the browser turns HTML, CSS, and JS into pixels — parsing, DOM/CSSOM, render tree, layout, paint, composite.

DOMCSSOMRender treeRender-blocking
Watch on YouTube ↗

The browser receives a pile of HTML, CSS, and JavaScript and somehow turns it into colored pixels on your screen. The exact sequence it follows is the Critical Rendering Path (CRP) — and once you can see each step, you can see exactly where it's safe to save time and where you're accidentally blocking the whole thing.

The big picture

From bytes to pixels, stage by stage

The CRP has two halves: parsing (turn the downloaded files into in-memory trees) and rendering (turn those trees into pixels). Walk through all six stages on a tiny example page and watch the viewport fill in only at the very end.

InteractiveThe Critical Rendering Path
The Critical Rendering Path
DOM tree
html
head
title
style
body
h1 “Hello”
p text
img
div.promo
1/6 · Parse HTML
HTML → DOM. Tokens (<html>, <body>…) build the DOM tree node by node.
Parse HTML→DOM and CSS→CSSOM, combine into the render tree, compute layout, paint pixels, composite layers. Nothing is visible until layout + paint.
Step 1 & 2

Parsing: the DOM and the CSSOM

As HTML bytes arrive, the browser tokenizes them — recognizing <html>, <body>, <h1> — and assembles a tree starting from the root: the DOM. More nodes means more time, so a giant DOM is itself a performance cost.

CSS is parsed into a parallel structure, the CSSOM: the same tree shape, but each node carries its computed styles, resolved by the cascade from general rules to specific ones. Building it is typically only a few milliseconds even on big sites.

The pre-scanner is your friend
While the main parser works line by line, a separate pre-scanner races ahead through the HTML and kicks off requests for high-priority resources early — so by the time the parser reaches a <link> or <img>, it's often already downloading. This is also why where you put resource hints matters (a later lesson).
The expensive trap

Render-blocking resources

Here's where the path stalls. The browser cannot paint until it has the CSSOM (an unstyled flash would be unacceptable), so an external stylesheet is render-blocking. Worse, a plain <script> is parser-blocking: the browser stops building the DOM, downloads and runs the script, then resumes. Toggle the options and watch the First-Paint marker move.

InteractiveWhat pushes First Paint later?
Script loading strategy
HTML
styles.css
app.js (sync)
First Paint 320 ms
A plain <script> is parser-blocking: the browser stops building the DOM to fetch and run it, pushing first paint out to 320 ms. The external stylesheet is still render-blocking — inline it to paint sooner.
A synchronous script blocks the parser; an external stylesheet blocks rendering. defer/async free the parser; inlining critical CSS frees rendering.

The fixes, in code

non-blocking scripts
<!-- Blocks the parser: avoid in <head> -->
<script src="/app.js"></script>

<!-- Downloads in parallel, runs after parsing, keeps order -->
<script src="/app.js" defer></script>

<!-- Downloads in parallel, runs ASAP (order not guaranteed) -->
<script src="/analytics.js" async></script>
Key idea
Use defer for your app code (needs the DOM, must stay ordered) and async for independent third-party scripts like analytics. Inline the small amount of critical CSS and load the rest asynchronously.
Step 3

What actually makes it into the render tree

The render tree is DOM + CSSOM, filtered to visible nodes only. This is where display:none elements vanish completely — they stay in the DOM but never reach layout or paint. That's different from visibility:hidden, which is in the render tree and does take up space. Flip between them:

Interactivedisplay:none vs visibility:hidden
display:none vs visibility:hidden
visiblevisibility:hiddendisplay:none
In the DOM?
In the render tree?
Takes up layout space?
Painted (visible)?
Rendered viewport
.promo
Fully visible and painted.
1/3 · visible
visible — fully in the DOM, render tree, laid out, and painted.
display:none is dropped from the render tree (no box, no gap). visibility:hidden stays laid out — invisible, but still occupying space.
Steps 4–6

Layout, paint, and composite

With the render tree in hand, three steps finish the job:

Layout computes the exact position and size of every box, relative to the viewport. The first pass is “layout”; any later recalculation triggered by a change (a resize, a DOM mutation, reading an element's size) is a reflow — and reflows are expensive because they can cascade across the page.

Paint fills in the pixels — text, colors, borders, images. The first pixels to land are the First Paint. Composite then layers overlapping elements in the correct order (using hints like z-index and GPU layers) to produce the final frame.

Layout / reflowPaint
ComputesPosition & size of boxesActual colored pixels
Triggered byGeometry changes (size, position, DOM)Appearance changes (color, shadow)
Relative costExpensive — can cascadeCheaper, but still real
Cheapest to animatetransform / opacity skip layout & paint — composite only
Why transform beats top/left
Animating left or width forces layout + paint every frame. Animating transform or opacity can be handled by the compositor alone — no layout, no paint — which is why smooth 60fps animations stick to those two properties.
Q1Sort each scenario
Which half of the path does each step belong to — parsing or rendering?
Building the DOM from HTML tokens
Building the CSSOM
Computing box positions and sizes
Drawing pixels for text and borders
Layering overlapping elements by z-index
Q2Multiple choice
An element has display:none. Where does it appear?
Q3Multiple choice
Which script tag keeps the parser running AND preserves execution order?
Q4Multiple choice
You want a buttery 60fps move animation. Which property is cheapest?
Key takeaways
  • The CRP is parse (DOM + CSSOM) → render (render tree → layoutpaintcomposite).
  • External CSS is render-blocking; a plain script is parser-blocking. Inline critical CSS; use defer/async for scripts.
  • The render tree holds only visible nodes — display:none is excluded entirely.
  • Reflows (re-layout) are expensive and can cascade; minimize what triggers them.
  • Animate transform/opacity to stay on the compositor and skip layout + paint.
← Previous
1. Introduction
Next →
3. Introduction to Web Vitals