Frontend Labs
⋯

21. The critical rendering path: bytes → DOM → CSSOM → render tree → layout → paint → composite

This is the pipeline every byte of HTML/CSS/JS travels through before a single pixel reaches your screen — the single most important mental model for web performance work.

1. PARSE HTML  →  DOM tree
   The HTML parser is incremental and CAN be blocked by synchronous <script>
   tags encountered mid-document (the parser must run and finish the script
   before it can safely continue, since document.write() could change what
   comes next).

2. PARSE CSS   →  CSSOM tree
   CSS parsing is render-blocking by design: no partial styles are ever
   shown, because a rule later in the file can override one seen earlier
   (correctness requires the whole stylesheet before computing final styles).

3. DOM + CSSOM  →  RENDER TREE
   Only visible nodes with computed styles (display:none nodes are excluded
   entirely; visibility:hidden nodes ARE included, just invisible — this
   distinction is why display:none saves layout cost and visibility:hidden
   doesn't).

4. RENDER TREE  →  LAYOUT (a.k.a. "reflow")
   Computes the exact position and size of every render-tree node. Expensive:
   changing one node's width can cascade and force recomputing its entire
   subtree AND everything after it in document flow.

5. LAYOUT  →  PAINT
   Rasterizes: fills in pixels — text, colors, borders, shadows, images —
   for each render-tree node, typically onto separate "layers".

6. PAINT  →  COMPOSITE
   The GPU combines the painted layers into the final frame you see,
   applying transforms and opacity cheaply without re-painting (§22).

Why this pipeline matters for every performance decision you make: which CSS properties you animate determines how far back up this pipeline the browser must re-run on every frame.

Changing `width`, `top`, `left`, `font-size`      -> triggers LAYOUT (step 4) + PAINT + COMPOSITE
Changing `background-color`, `box-shadow`, `color` -> triggers PAINT (step 5) + COMPOSITE, skips layout
Changing `transform`, `opacity`                     -> triggers ONLY COMPOSITE (step 6)
                                                        -> can run entirely on the GPU thread,
                                                           no main-thread JS involved on every frame

This is the reason every serious animation guideline says "animate transform/opacity, never top/left/width" — it's not a style preference, it's the difference between a 60fps animation running off the main thread and one that re-runs full layout on every single frame.

// BAD: forces synchronous layout on every animation frame ("layout thrashing")
function animateBad(el, targetLeft) {
  function step() {
    const current = parseFloat(getComputedStyle(el).left); // READ triggers layout
    el.style.left = (current + 1) + "px";                   // WRITE invalidates layout
    if (current < targetLeft) requestAnimationFrame(step);
  }
  step();
}

// GOOD: composite-only, runs on the GPU, never touches layout
function animateGood(el, targetX) {
  el.style.transform = `translateX(${targetX}px)`;
  el.style.transition = "transform 300ms ease-out";
}

Generated from real-life-JS-guide.md · 8 Oct 2026 16:36