Critical Rendering Path (CRP) — how the browser renders 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.
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
transformoropacityon a compositor layer → Composite only — much cheaper.
Optimizing the CRP
Reduce render-blocking
- Inline critical CSS for above-the-fold; lazy-load the rest.
async/deferon scripts — async runs as soon as it's downloaded; defer runs after parsing.mediaattribute 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, notleft/top/width— composite-only. - Use
will-change(sparingly) ortransform: translateZ(0)to promote a layer. - Use
content-visibility: autoto 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.