The critical rendering path is the story of your first paint
Between the moment the HTML arrives and the moment the first pixel appears, the browser runs a fixed sequence of steps: parse the HTML into a DOM, parse the CSS into a CSSOM, combine them into a render tree, lay it out, and paint. That sequence is the critical rendering path, and the reason it is worth knowing is that two things you control — CSS and synchronous JavaScript — can stall it. A page paints sooner not by doing the steps faster but by removing what blocks them, and to remove a blocker you have to know where it sits on the path.
CSS is render-blocking by design
The render tree needs the CSSOM, and the CSSOM is not ready until the browser
has downloaded and parsed every stylesheet it has seen. So a single large
<link> in the <head> holds the first paint hostage — the browser will not
show a half-styled page. The fix is to ship the styles the first screen needs
inline and defer the rest, or to mark a non-critical stylesheet as not blocking:
<!-- critical styles inline: no round-trip before first paint -->
<style>/* just what the above-the-fold layout needs */</style>
<!-- the rest loads without blocking render, then applies -->
<link rel="stylesheet" href="/full.css" media="print" onload="this.media='all'">
A synchronous script blocks parsing
When the parser hits a plain <script src>, it stops — it must fetch and run
that script before continuing, because the script might call
document.write and change the DOM it is building. Put that script in the
<head> and you have paused DOM construction before the body even exists. The
two attributes that fix this tell the browser the script does not need to block:
<!-- defer: fetch in parallel, run in order AFTER the DOM is parsed -->
<script src="/app.js" defer></script>
<!-- async: fetch in parallel, run as soon as it lands (order not guaranteed) -->
<script src="/analytics.js" async></script>
Use defer for your application code (it needs the DOM and it needs to run in
order); use async for independent scripts like analytics that touch nothing
else. The one thing you almost never want is a bare, synchronous script in the
<head>.
The path is the mental model for every “why is it slow?”
Most first-paint problems reduce to one of these two blockers plus a third: a render-tree element that has to wait for a resource, like a web font or the LCP image. Once you see the page as this pipeline, the tactics fall out — inline critical CSS, defer scripts, preload the hero image, subset the font — because each one removes a specific stall from a specific step. The render-strategy exercise makes you choose where a page should render given its path, which is the same reasoning applied one level up. Optimising a first paint is not folklore; it is reading this diagram and asking, at each arrow, “what is the browser waiting for here, and can I stop making it wait?”