The critical path is what has to arrive before anything is shown
The browser cannot paint until it has the HTML it has parsed so far and every stylesheet that applies to it. Anything else — images below the fold, analytics, the chat widget — can arrive later without the user noticing. The work of making a page feel fast is mostly deciding which is which.
HTML (enough of it) + every render-blocking stylesheet → first paint
Scripts: three behaviours
defer is the right default for application code: it downloads in parallel and runs in order once the DOM exists. async is for genuinely independent scripts — analytics — where "whenever" is fine and running before the page is built is harmless.
An inline script blocks the parser too, and it also blocks on any stylesheet above it, because it might ask for a computed style. An inline script between your CSS and your content is a stall nobody sees in the network panel.
| Tag | Blocks the parser | Runs |
|---|---|---|
<script src> | yes — parsing stops until it has run | immediately, in order |
<script defer src> | no | after parsing, in order, before DOMContentLoaded |
<script async src> | no | whenever it arrives — order not guaranteed |
<script type="module"> | no (deferred by default) | after parsing, in order |
Stylesheets block painting, not parsing
Every blocking stylesheet is another round trip before anything appears. Splitting the rules the first screen needs out of the rest — or inlining that small part in the document — is the oldest trick in this lesson and still the most effective.
<link rel="stylesheet" href="/app.css"> <!-- blocks paint -->
<link rel="stylesheet" href="/print.css" media="print"> <!-- does not -->
<link rel="stylesheet" href="/wide.css" media="(min-width:60em)"><!-- only on wide screens -->
Telling the browser what it will need
<link rel="preconnect" href="https://api.doors.test"> <!-- open the connection now -->
<link rel="preload" href="/fonts/space.woff2" as="font" crossorigin> <!-- fetch it at high priority -->
<link rel="prefetch" href="/next-page.js"> <!-- for the NEXT navigation, idle -->
<link rel="modulepreload" href="/app.js">
Preload raises a resource above others; three or four of them is a plan, twenty is a re-ordering with extra steps. The browser already prioritises well — hint only for things it cannot discover early, like a font referenced from a CSS file it has not parsed yet.
Fonts, and the invisible text
Without font-display, most browsers hide the text for up to three seconds waiting for the font. swap shows it at once in a fallback and swaps — a visible reflow, and far better than a blank page. optional is stricter still: use the font only if it arrives almost immediately, otherwise never for this visit.
@font-face {
font-family: "Space Grotesk";
src: url(/fonts/space.woff2) format("woff2");
font-display: swap; /* show the fallback immediately, swap when it loads */
}