How bytes become pixels — DOM → CSSOM → Render Tree → Layout → Paint → Composite — and the two
resources that block it. The backbone topic that connects resource hints,
async/defer, and Core Web Vitals.
The Critical Rendering Path is the fixed sequence of steps the browser runs to turn HTML, CSS, and JS into pixels. Memorize it in three moves — construct the DOM and CSSOM in parallel (with JS able to interrupt the DOM branch to fetch/compile/run), combine them into the render tree, then render through layout → paint → composite. The shape matters: drawing the two branches merging — and showing where JS jams the parser — is what separates a Lead answer from a memorized list.
<script>
JS
▶
display:none)<script> it stops and runs the engine's own pipeline — fetch → parse + compile (bytecode)
→ execute — before resuming HTML parsing. That's why JS is parser-blocking; it also waits on any
pending CSSOM (it may read computed styles) and can mutate the DOM mid-build. async/defer
(§3) move this step off the critical parse.display:none is
excluded; visibility:hidden stays (it occupies space).transform / opacity
animations can run here alone — no layout, no paint — which is why they're cheap.Sources: web.dev — Understand the critical path · MDN — Critical rendering path · MDN — <script> parsing & execution
Knowing the six boxes is table stakes. The Lead-level insight is which resources stall the pipeline, and why. There are exactly two villains.
The browser won't render anything until the CSSOM is complete. Why? Any later rule could override an earlier one — paint now and you'd risk a flash of wrong styles. So CSS blocks the render tree by design.
A plain <script> pauses HTML parsing — the browser must fetch + execute it before
continuing, because JS can mutate the DOM. And: a script waits for any pending CSSOM, since it might
read computed styles.
“Two things stall the page: CSS blocks painting, JS blocks parsing. So two fixes: ship less
critical CSS, and get JS off the parser with defer/async.”
That second sentence — “get script out of the parser's way” — is exactly what async and
defer do. This is the rapid-fire question right after resource hints, so let's nail it.
<script src="a.js"></script> // parser STOPS: fetch + run, then resume
<script src="a.js" async></script> // fetch in parallel, run ASAP, order NOT guaranteed
<script src="a.js" defer></script> // fetch in parallel, run AFTER parse, IN ORDER
| Attribute | Download | Executes | Order kept? | Blocks parser? |
|---|---|---|---|---|
(none) |
blocks parser | immediately, mid-parse | yes | yes |
async |
parallel | as soon as downloaded | no (whoever lands first) | only briefly, when it runs |
defer |
parallel | after parse, before DOMContentLoaded |
yes | no |
deferOrder-dependent code, your framework bundle, anything that touches the DOM. Runs after the document is parsed, in order — non-blocking and predictable.
asyncSelf-contained third-party scripts — analytics, tag managers — that depend on nothing and that nothing depends on. Runs the instant it lands.
async'd all our scripts for speed and the app broke intermittently.”
Of course — async gives no order guarantee, so a script ran before its dependency.
async is for independent scripts only; for anything order-dependent, defer.
“Both download in parallel. defer = runs after parsing, in order — app code.
async = runs whenever it lands, no order — independent tags only. Use
async on ordered code and you get flaky bugs.”
Even when a sync script blocks the parser, browsers run a secondary preload scanner that races ahead in the raw HTML and kicks off downloads for images, CSS, and scripts it spots. It's why fetches overlap instead of running strictly one-by-one — and why an inline script that injects resources defeats it (the scanner can't see resources that don't exist in the markup yet).
Sources: MDN — <script> (async/defer) · web.dev — Render-tree, Layout & Paint
Optimization = shorten the path to first paint. Three concrete levers, all derived from §2:
media tricks / rel=preload swap). Fewer bytes in the CSSOM = earlier first paint.
defer app code, async independent tags, code-split so
less executes before interactive.Concept: the CRP is DOM+CSSOM→render tree→layout→paint→composite; CSS blocks render, sync JS blocks the parser. Trade-off: inlining critical CSS speeds first paint but bloats the HTML and is hard to cache — so I automate extraction, not hand-maintain it. Anchor: “On our listing page we inlined ~14KB critical CSS and deferred the rest; FCP dropped a beat.” Impact: first paint feeds LCP, a ranking + conversion lever for the platform. Invite: “On a content page I'd lean SSR + critical CSS; on an app shell I'd weigh it against caching.”
Notice how this chains to Lesson 01: resource hints make the critical bytes arrive sooner; the CRP is what the browser does with them. Next lesson connects the output of this path to the metrics — Core Web Vitals (LCP / INP / CLS).
Pick an answer; you get instant feedback. Push-back style, like the round.
1. Why is CSS render-blocking?
2. You have three app scripts where b.js needs a.js, and a separate
analytics tag. Best loading strategy?
3. A plain <script> sits between a slow
<link rel="stylesheet"> and your content. What actually happens?
They want to hear that JS also waits on CSS.
4. Which animation can run on the compositor alone — no layout, no paint?
5. Best one-sentence framing of the CRP optimization goal?
0 / 5 answered