How does the browser render a webpage (Critical Rendering Path)
HTML → DOM, CSS → CSSOM, combined into the Render Tree → Layout (geometry) → Paint (pixels) → Composite (layers). JS can block parsing; CSS blocks rendering. Optimizing the CRP = minimize/defer blocking resources, inline critical CSS, reduce bytes — the foundation of fast first paint.
The Critical Rendering Path (CRP) is the sequence of steps the browser takes to turn HTML, CSS, and JS into pixels on screen.
The steps
1. Parse HTML → DOM. The browser parses HTML into the DOM tree — the structured representation of the document.
2. Parse CSS → CSSOM. All CSS (external, internal, inline) is parsed into the CSSOM — the tree of styles. CSS is render-blocking: the browser won't paint until the CSSOM is ready, because it can't render an element without knowing its styles.
3. DOM + CSSOM → Render Tree. The two are combined into the render tree — only the nodes that will be visible (display: none elements are excluded; visibility: hidden ones are kept but invisible). Each node has its content + computed styles.
4. Layout (Reflow). The browser computes the geometry — exact position and size of every render-tree node — given the viewport. This is where box dimensions, flex/grid resolution, etc. happen.
5. Paint. The browser fills in pixels — text, colors, borders, shadows, images — into layers.
6. Composite. Layers are drawn to the screen in the correct order. Some properties (transform, opacity) can be handled here on the GPU without re-layout or re-paint — which is why they're cheap to animate.
What blocks what
- CSS blocks rendering — nothing paints until the CSSOM is built. Large/slow CSS delays first paint.
- JS blocks parsing — a
<script>(withoutasync/defer) pauses HTML parsing, downloads, and executes before parsing resumes. And because JS can read styles, it also waits on pending CSS. So scripts can block both DOM construction and rendering.
Optimizing the CRP
The goal: get to first meaningful paint with the fewest, smallest, least-blocking resources.
- Minimize render-blocking CSS — inline critical CSS for above-the-fold content; load the rest async/deferred.
- Defer JS —
defer(orasync) on scripts so they don't block parsing; load non-critical JS later; code-split. - Reduce bytes — minify, compress, tree-shake; fewer round-trips.
- Prioritize above-the-fold — get the visible content rendering first; lazy-load the rest.
- Avoid layout thrash — batch DOM reads/writes; prefer compositor-only properties (
transform/opacity) for animation.
The framing
"HTML becomes the DOM, CSS becomes the CSSOM, they combine into the render tree of visible nodes, then Layout computes geometry, Paint fills pixels, and Composite draws the layers — with transform/opacity handled on the GPU at the composite step. The key constraints: CSS is render-blocking and JS is parser-blocking. So optimizing the CRP means inlining critical CSS, deferring JS, cutting bytes, and prioritizing above-the-fold content — that's the foundation of a fast first paint."
Follow-up questions
- •Why does CSS block rendering but a deferred script doesn't?
- •What's the difference between layout, paint, and composite?
- •Why are transform and opacity cheap to animate?
- •What is critical CSS and how do you use it?
Common mistakes
- •Forgetting that CSS is render-blocking.
- •Putting render-blocking scripts in the head without async/defer.
- •Confusing layout (geometry) with paint (pixels).
- •Not knowing display:none elements are excluded from the render tree.
- •Animating layout-triggering properties instead of transform/opacity.
Performance considerations
- •The CRP directly determines First Contentful Paint and Largest Contentful Paint. Render-blocking CSS and parser-blocking JS are the main levers; layout and paint cost recurs on every change, while composite-only updates are the cheapest.
Edge cases
- •A large synchronous script in the head stalling first paint.
- •Web fonts blocking text rendering (FOIT/FOUT).
- •visibility:hidden vs display:none in the render tree.
- •Forced synchronous layout (layout thrashing) from interleaved reads/writes.
Real-world examples
- •Inlining critical CSS and deferring the rest to speed up FCP.
- •Adding defer to analytics/third-party scripts so they don't block parsing.