Bcorre

Critical Rendering Path: How the Browser Draws the First Frame

What happens between 'received HTML' and 'user sees content'. A step-by-step breakdown and where time is typically lost.

Between the moment "the browser received the first byte of HTML" and the moment "the user sees content," a great deal of invisible work takes place. Understanding this chain — the Critical Rendering Path, CRP — is what separates an SEO specialist who optimizes Core Web Vitals by guesswork from one who knows exactly where to look for bottlenecks.

This article walks through the CRP step by step and draws practical conclusions for each stage.

CRP Stages

There are five of them:

  1. Construct the DOM — building the DOM tree from HTML
  2. Construct the CSSOM — building the style tree from CSS
  3. Render Tree — merging the DOM and CSSOM
  4. Layout — computing geometry (positions, element sizes)
  5. Paint + Composite — drawing pixels on screen

JavaScript slots in between these stages and can break everything. Let's go through each one.

Stage 1: DOM Construction

The browser receives HTML byte by byte. The parser converts tags into DOM tree nodes line by line.

What blocks it:

  • Encounters a <script> without async/defer — stops parsing, downloads the JS, executes it, then resumes
  • Encounters <link rel="stylesheet"> — continues parsing, but cannot start rendering without the CSSOM
  • Encounters <iframe> — opens a separate child context, does not block the main one

What to speed up:

  • defer or async on scripts (see render-blocking resources)
  • HTML streaming from the server (deliver HTML as it becomes ready, without waiting for the full page)
  • Reducing HTML size (gzip/brotli compression)

A typical HTML document of 50–100 KB is parsed in 10–30 ms. This is rarely the bottleneck. The real bottleneck is usually downloading the HTML over the network.

Stage 2: CSSOM Construction

CSS blocks rendering. The browser must build a complete CSSOM before the first paint.

How it works:

  1. The browser encounters <link rel="stylesheet" href="main.css"> in the HTML
  2. It fetches main.css in a separate request
  3. It parses the CSS and builds the rule tree
  4. If the CSS contains @import — it repeats the process recursively (slow — see @import in CSS)

What blocks it:

  • Large CSS files (200+ KB uncompressed)
  • @import chains
  • CSS from external domains (requires DNS lookup, handshake)
  • CSS blocked by a slow server

What to speed up:

  • Inline critical CSS in HTML — removes blocking for above-the-fold content
  • Preload the main CSS — <link rel="preload" href="main.css" as="style">
  • Brotli compression
  • Removing unused CSS (PurgeCSS)

Stage 3: Render Tree

Once the DOM and CSSOM are ready, the browser builds the Render Tree — the DOM with computed styles applied to each node. Hidden elements (display: none) are excluded from the Render Tree.

What blocks it:

  • DOM not yet ready (heavy HTML, synchronous scripts)
  • CSSOM not yet ready (large CSS, @import)

The stage itself is fast (5–20 ms) — it simply waits for the DOM + CSSOM.

Stage 4: Layout (Reflow)

The browser computes the geometry of each node — where it will appear on screen, what size it will be, and how it relates to its neighbors.

What slows it down:

  • Thousands of nodes in the DOM (a page with 50,000 elements takes a long time to calculate)
  • Complex flexbox/grid with recalculation
  • Viewport resize events
  • JavaScript that changes geometry between paints (for example, dynamically adding elements)

What to speed up:

  • Minimize the DOM — keep the structure simple, avoid unnecessary wrappers
  • Use contain: layout style on isolated components — the browser will not recalculate everything when one block changes
  • Avoid "forced layouts" in JS — this happens when you modify an element and immediately read its geometry (offsetWidth, clientHeight), forcing the browser to recalculate right away

Group reads and writes together:

// Bad — N reflows
elements.forEach(el => {
  el.style.width = el.offsetWidth + 10 + 'px';  // read+write on every element
});

// Good — 1 reflow
const widths = elements.map(el => el.offsetWidth);  // all reads
elements.forEach((el, i) => {
  el.style.width = widths[i] + 10 + 'px';  // all writes
});

Stage 5: Paint + Composite

The final step — actually drawing pixels. The browser arranges elements into layers, renders each layer separately, then composites them into the final frame.

What slows it down:

  • Large areas with heavy styles (shadows, filters, gradients)
  • Animating width, height, top, left — requires geometry recalculation
  • backdrop-filter — the most expensive effect in modern CSS

What to speed up:

  • Animate only with transform and opacity — they run on the GPU and do not require a paint
  • will-change: transform on animated elements — hints to the browser to allocate a separate compositor layer
  • Use backdrop-filter sparingly (if truly needed — apply it to small areas only)
  • Use content-visibility: auto for off-screen blocks — skips painting content that is not visible

Where JavaScript Fits In

JS can slot in at any point in the CRP and disrupt everything.

Synchronous JS in <head>:

Blocks DOM parsing. The parser stops, the JS is downloaded, parsed, and executed. Only then does the parser continue.

<head>
  <script src="heavy.js"></script>          <!-- blocks, bad -->
  <script src="heavy.js" defer></script>    <!-- waits for DOM, then executes -->
  <script src="heavy.js" async></script>    <!-- executes when ready, may interrupt -->
</head>

JS that manipulates the DOM:

If JS adds elements — Layout is recalculated. If it changes styles — Paint is recalculated. Minimize DOM manipulation from JS, especially during page load.

JS that measures geometry:

element.offsetWidth, getBoundingClientRect() — these trigger a "sync layout." They force the browser to recalculate Layout immediately. Calling them frequently causes significant overhead.

Metrics and Where to Look

In Chrome DevTools → Performance:

  1. Record a page load (3–5 seconds)
  2. Open the Main thread timeline
  3. The color coding shows:
    • Yellow (Scripting) — JS
    • Purple (Rendering) — Layout
    • Green (Painting) — Paint
    • Blue (Loading) — network

Wherever a color takes up a lot of space — that is your bottleneck.

Long Tasks (>50 ms) are marked with a red triangle in the timeline. They block the main thread and degrade INP and LCP.

Connection to Core Web Vitals

CRP directly affects all three metrics:

  • LCP — this is the moment when the main visual element completes Layout + Paint. The faster the CRP, the better the LCP.
  • CLS — if Layout is recalculated after the first Paint, elements can "jump," increasing CLS.
  • INP — if a JS handler forces the browser to perform Layout/Paint across more than one frame, INP drops.

For more detail, see Core Web Vitals 2026.

Summary

The Critical Rendering Path is not a theoretical concept — it is a concrete chain of operations that can be accelerated step by step:

  1. Streaming + HTML compression
  2. Inline critical CSS + preload the rest
  3. Defer/async JS
  4. Minimize the DOM
  5. GPU-only animations (transform/opacity)

Understanding CRP lets you optimize with precision, rather than working through a checklist from PageSpeed Insights.

Want a detailed CRP analysis of your site? Get in touch. Free within 2 business days.

Сайт + SEO + GEO/AEO

This is part of our service Видимость под ключ

Сайт, SEO, GEO/AEO, хостинг и аналитика в одной подписке

Go to service →
Take it further?

Need an expert eye on your project?

We do an express audit in 2 business days: showing where your site is losing traffic and what to fix first.

Discuss project