Back to Browser Internals
Browser Internals
medium
mid

What is the Critical Rendering Path and how does the browser render a page?

HTML → DOM. CSS → CSSOM. DOM + CSSOM → Render tree. Layout (compute geometry). Paint (rasterize). Composite (layers). Each step blocks the next on the main thread. Optimize by minimizing render-blocking CSS, deferring non-critical JS, sizing media, and isolating animations to transform/opacity for compositor-only paint.

5 min read·~15 min to think through

The Critical Rendering Path is how the browser turns HTML/CSS/JS into pixels on screen. Knowing it tells you where every perf optimization lives.

The steps

1. Parse HTML → DOM

Streaming HTML parser builds the DOM. Encountering a <script> (without async/defer) blocks parsing. Encountering a <link rel="stylesheet"> blocks rendering (and blocks scripts that come after).

2. Parse CSS → CSSOM

CSS is fully blocking — the browser won't paint until all stylesheets are downloaded and parsed. The CSSOM is the styled tree of rules.

3. Render tree

Combine DOM + CSSOM → only visible nodes (skip display:none) with computed styles.

4. Layout (a.k.a. reflow)

Compute the geometry of every render-tree node — x, y, width, height. Expensive; triggered by viewport changes, DOM insertions, style changes that affect geometry, reading certain properties (offsetTop, getBoundingClientRect) right after a write (layout thrashing).

5. Paint

Fill in the pixels for each render-tree node — text, colors, shadows, images. Can happen on multiple layers.

6. Composite

Combine layers into the final image — often on the GPU. Cheapest step.

Why this matters

Every change can re-trigger steps from some point down:

  • Change a layout-affecting property (width, top, font-size) → Layout → Paint → Composite.
  • Change a paint-only property (color, background) → Paint → Composite.
  • Change transform or opacity on a compositor layerComposite only — much cheaper.

Optimizing the CRP

Reduce render-blocking

  • Inline critical CSS for above-the-fold; lazy-load the rest.
  • async / defer on scripts — async runs as soon as it's downloaded; defer runs after parsing.
  • media attribute on <link> to mark a stylesheet as non-blocking for the current viewport (media="print").
  • rel="preload" for resources the browser will need (fonts, hero images) to start them earlier.

Reduce request size and count

  • HTTP/2 multiplexing helps but doesn't eliminate the value of bundling.
  • Compress (Brotli) and minify.
  • Tree-shake unused code.

Reduce layout/paint cost

  • Avoid layout thrashing — batch reads, then writes (or use requestAnimationFrame).
  • Animate transform / opacity, not left/top/width — composite-only.
  • Use will-change (sparingly) or transform: translateZ(0) to promote a layer.
  • Use content-visibility: auto to skip layout/paint for off-screen content.

Reduce image cost

  • Responsive images (srcset); modern formats (WebP/AVIF); lazy-load below-the-fold; explicit dimensions to prevent CLS.

Reduce JS parse/eval

  • Code-split per route.
  • Defer non-essential JS.
  • Skip polyfills for modern browsers (module/nomodule).

Core Web Vitals map onto CRP

  • LCP (Largest Contentful Paint) — driven by HTML/CSS blocking, hero image, server response.
  • CLS (Cumulative Layout Shift) — caused by layout work after the first paint (unsized media, late-inserted content).
  • INP (Interaction to Next Paint) — main-thread responsiveness — JS work, layout thrash.

Interview framing

"HTML becomes the DOM and CSS becomes the CSSOM; they merge into a render tree; layout computes geometry; paint fills pixels; composite combines layers. CSS is fully blocking — nothing paints until it's parsed — and synchronous scripts block parsing. To optimize the path: inline critical CSS, defer non-critical JS, size media, animate transform/opacity for compositor-only updates, and use content-visibility: auto to skip off-screen work. The Web Vitals (LCP, CLS, INP) map directly to specific stages of this pipeline."

Follow-up questions

  • Why does adding async to a script change CRP?
  • Why is animating transform cheaper than animating top/left?
  • What causes layout thrashing and how do you avoid it?
  • How does inlining critical CSS help LCP?

Common mistakes

  • Animating layout-affecting properties for transitions.
  • Big render-blocking CSS files for above-the-fold.
  • Layout thrashing — read/write/read/write loops.
  • Unsized images causing CLS.

Performance considerations

  • The whole topic IS performance. The general rule: keep the main thread free, animate on the compositor, size everything, and defer what isn't above the fold.

Edge cases

  • FOUC when CSS loads late.
  • Fonts blocking text — use `font-display: swap`.
  • Compositor-promoted layer memory cost.

Real-world examples

  • Chrome DevTools Performance panel shows each stage.
  • Next.js automatic critical CSS inlining and font subsetting.

Senior engineer discussion

Seniors think in pipeline stages: 'this change triggers layout' vs 'this is composite-only', use the Performance panel to verify, and tie Core Web Vitals to specific stages — not vague 'speed it up' advice.

Related questions