awesome-everything RU
↑ Back to the climb

Networking & Protocols

Critical render path and Core Web Vitals

Crux How the browser turns bytes into pixels — the DOM/CSSOM merge, CSS render-blocking, the preload scanner, JavaScript deferral — and how LCP, INP, and CLS measure the user''''s experience of that pipeline.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at middle altitude — in the sky
◷ 14 min

The network portion of a page load is done in 150 ms. LCP fires at 3.2 seconds. The gap is the browser’s rendering pipeline — and most of it is caused by decisions you made: which CSS to load, where you placed scripts, whether images have dimensions, what JavaScript runs on the main thread before paint. Understanding this pipeline is the second half of page load performance.

The critical render path

After the browser receives the HTML bytes, rendering proceeds in several overlapping stages:

  1. HTML parse → DOM. The HTML parser builds the Document Object Model as bytes stream in. Parsing starts before the full body has arrived — streaming parse.
  2. CSS parse → CSSOM. When the browser encounters a <link rel="stylesheet"> tag, it fetches the CSS file and builds the CSS Object Model. Nothing paints until the CSSOM is ready — CSS is render-blocking. Why? Showing a page with unstyled content (FOUC — Flash Of Unstyled Content) and then reflowing it causes layout instability that degrades perceived performance. This is intentional, not a bug.
  3. DOM + CSSOM → render tree. The browser merges the DOM and CSSOM into a render tree containing only visible elements with their computed styles.
  4. Layout. The browser calculates the exact position and size of each element.
  5. Paint. The browser rasterises the render tree into pixels.
  6. Compositing. GPU layers are merged into the final frame.

JavaScript complicates this: a <script> tag without async or defer blocks HTML parsing. The parser pauses, the script is fetched and executed, then parsing resumes. This is why classic scripts placed in <head> dramatically slow first paint.

Resource typeBlocks HTML parse?Blocks render?Fix
CSS (stylesheet)NoYesinline critical CSS; preload
JS (classic, in <head>)YesYesadd defer or async
JS (async)PartiallyPartiallyexecutes when ready, not in order
JS (defer)NoNoexecutes after HTML parse, in order
Images (no dimensions)NoCauses CLSadd width/height attributes
Fonts (WOFF2)NoCauses FOIT/FOUTpreload + font-display:swap

The preload scanner

The browser runs a lightweight HTML tokeniser called the preload scanner ahead of the main parser. It looks for <link>, <script>, and <img> URLs and fires requests for them before the main parser has processed those tags. Without it, subresource discovery would be sequential — each resource only fetched after the main parser reached it.

The preload scanner is defeated by:

  • JavaScript that writes <script src="..."> via document.write.
  • Complex client-side routing that builds URLs after framework initialisation.
  • Lazy-loading schemes that hide URLs behind data-src attributes.

Modern frameworks (Next.js, Astro, SvelteKit) emit <link rel="preload"> hints automatically to compensate.

Resource hints

HintEffectCost
<link rel="dns-prefetch" href="//cdn.example.com">Resolves DNS in backgroundMinimal
<link rel="preconnect" href="//cdn.example.com" crossorigin>DNS + TCP + TLS in backgroundOne idle socket per origin
<link rel="preload" as="font" crossorigin>Fetches font before parser reaches itBandwidth
<link rel="modulepreload">Fetches ES module + its importsBandwidth
<link rel="prefetch">Fetches likely-next-navigation resource (low priority)Bandwidth when idle

fetchpriority attribute. You can override the browser’s default resource scheduling:

  • <img src="hero.jpg" fetchpriority="high"> — hero image loaded before other images.
  • <script src="analytics.js" fetchpriority="low"> — defer non-critical JS below CSS and fonts.

Core Web Vitals

Google’s Core Web Vitals quantify user-perceived performance. Search ranking uses field RUM data (real user measurements via Chrome UX Report), not synthetic Lighthouse scores.

LCP — Largest Contentful Paint. When the largest visible element finishes rendering. Usually the hero image or main heading. Good: under 2.5 s. Poor: over 4 s. Primary causes of poor LCP: slow server TTFB, render-blocking resources delaying the main image, large unoptimised image files.

INP — Interaction to Next Paint (replaced FID in March 2024). The longest delay between any user input (click, tap, keypress) and the resulting visual update across the entire page lifecycle. Good: under 200 ms. Poor: over 500 ms. Primary cause: long tasks on the main thread holding the UI unresponsive.

CLS — Cumulative Layout Shift. Unexpected movement of visible elements during page load. Score: sum of (impact fraction × distance fraction) for each unexpected shift. Good: under 0.1. Primary causes: images without declared dimensions, ads inserting content, web fonts causing text reflow.

Why this works

Google uses field RUM data (real user measurements from Chrome UX Report) for ranking, not Lighthouse. A page scoring 95 in Lighthouse on your laptop may score 55 in CrUX if real users are on slow Android phones in poor signal. Optimise for the field, not the lab.

Quiz

Why does CSS block rendering by default?

Trace it
1/5

Trace what the browser does during HTML streaming — from first byte to LCP.

1
Step 1 of 5
First bytes of HTML arrive. What does the browser do immediately?
2
Locked
Parser encounters <link rel=stylesheet href=main.css>. HTML parsing continues. Rendering?
3
Locked
Parser encounters <script defer src=app.js>. What happens?
4
Locked
CSS arrives. CSSOM built. Render tree constructed. Browser paints first frame. What metric fires?
5
Locked
Hero image loads 400 ms after HTML. LCP fires. What determined whether LCP was Good or Poor?
Order the steps

Order these interventions by their impact on LCP (highest to lowest, assuming LCP is the hero image):

  1. 1 Cache HTML at CDN edge so TTFB drops from 300 ms to 20 ms
  2. 2 Add <link rel=preload as=image> for the hero image so it fetches in parallel with CSS
  3. 3 Convert the hero image from JPEG to WebP (30% smaller file size)
  4. 4 Add fetchpriority=high to the hero <img> tag
  5. 5 Remove render-blocking analytics script from <head> (move to defer)
Quiz

What is INP measuring that FID did not?

Recall before you leave
  1. 01
    Why is the preload scanner critical to fast page loads, and what defeats it?
  2. 02
    What causes CLS, and how do you fix it?
  3. 03
    Google ranks pages using field RUM data, not Lighthouse. What is the practical implication?
Recap

The browser’s critical render path converts bytes into pixels across six stages: HTML parse, CSS parse, render tree construction, layout, paint, and compositing. CSS is render-blocking — nothing paints until the CSSOM is ready — making it the highest-priority resource to deliver. JavaScript without defer or async blocks HTML parsing, delaying everything. The preload scanner rescues you by firing requests for subresources ahead of the main parser, but it is defeated by dynamically injected URLs. Core Web Vitals (LCP, INP, CLS) measure the user’s perception of this pipeline; Google uses field RUM data (real Chrome users), not Lighthouse scores, for ranking. The four highest-impact fixes are: CDN-cache HTML (reduce TTFB), preload the hero image, defer non-critical JS, and add image dimensions.

Connected lessons
appears again in165
Continue the climb ↑Proxy intercepts and security gates: rate limiters, WAF, mTLS
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.