Jour 26 Day 26 · mardi 8 septembre 2026 Tuesday 8 September 2026 Web Intermédiaire
Le navigateur sous le capot The browser under the hood
Du HTML aux pixels : parsing, render tree, reflow, compositing, multi-process et stockage — tout ce qu'un recruteur attend derrière « que se passe-t-il quand la page s'affiche ? ». From HTML to pixels: parsing, render tree, reflow, compositing, multi-process and storage — everything a recruiter expects behind "what happens when the page renders?".
L’essentiel
Le navigateur n’est pas une boîte noire qui « affiche du HTML » : c’est un petit système d’exploitation, avec un moteur de rendu (Blink, WebKit, Gecko), un moteur JavaScript (V8, JavaScriptCore, SpiderMonkey), une pile réseau et une architecture multi-process. La question « que se passe-t-il entre la réponse du serveur et les pixels à l’écran ? » est un grand classique d’entretien front-end : elle teste d’un coup votre compréhension du rendu, de la performance et de la sécurité.
La chaîne complète s’appelle le critical rendering path : l’ensemble minimal d’étapes et de ressources bloquantes que le navigateur doit franchir avant le premier rendu. La maîtriser, c’est savoir pourquoi une page est lente — et où agir.
Trois niveaux de coût à retenir d’emblée :
| Changement | Étapes rejouées | Coût |
|---|---|---|
width, margin, ajout de nœud | Layout → Paint → Composite | Élevé (reflow) |
color, background, visibility | Paint → Composite | Moyen (repaint) |
transform, opacity | Composite seul | Minime (GPU) |
Comment ça marche
Six étapes, toujours dans le même ordre :
- Parsing HTML → DOM — le parser lit le flux d’octets (le parsing est incrémental : il commence avant la fin du téléchargement) et construit le DOM, l’arbre d’objets vivant qui représente le document. Un
<script>classique bloque le parser : il pourrait fairedocument.write(), le navigateur doit donc l’exécuter avant de continuer. - Parsing CSS → CSSOM — les feuilles de style produisent le CSSOM. Le CSS est render-blocking (pas de rendu sans styles complets, sinon flash de contenu non stylé) et il bloque aussi l’exécution des scripts, qui pourraient lire des styles calculés.
- Render tree — fusion DOM + CSSOM : uniquement les nœuds visibles, avec leurs styles calculés.
display: noneen est exclu ;visibility: hiddeny reste (l’élément occupe sa place). - Layout (reflow) — calcul de la géométrie exacte de chaque boîte : position et taille, en cascade depuis la racine.
- Paint — rasterisation : chaque élément devient des pixels, répartis dans une ou plusieurs layers.
- Composite — le GPU assemble les layers dans le bon ordre. C’est pour ça que
transformetopacitysont quasi gratuits : ils ne touchent que cette étape.
HTML CSS
│ parsing │ parsing
▼ ▼
DOM + CSSOM
└────────────┬─────────────┘
▼
Render tree
▼
Layout (géométrie → reflow)
▼
Paint (pixels → repaint)
▼
Composite (GPU, layers)
Et les scripts dans tout ça ? Deux attributs changent la donne :
async— téléchargement en parallèle du parsing, exécution dès que le script est prêt, éventuellement en plein parsing, dans un ordre non garanti. Pour les scripts indépendants (analytics, pubs).defer— téléchargement en parallèle, exécution après la fin du parsing, dans l’ordre du document, juste avantDOMContentLoaded. Le bon défaut pour le code applicatif (et le comportement detype="module").
🎤 En entretien — « que se passe-t-il entre la réponse HTML et l’affichage ? » : déroulez le pipeline en six étapes (DOM, CSSOM, render tree, layout, paint, composite), précisez ce qui bloque quoi (script classique → parser ; CSS → rendu et scripts), et concluez sur le critical rendering path : moins de ressources bloquantes = premier rendu plus tôt. Une minute, structuré, imbattable.
Concepts clés à maîtriser
- Reflow vs repaint — le reflow recalcule la géométrie et se propage (changer la taille d’un parent repositionne ses enfants, parfois tout l’arbre) ; le repaint redessine des pixels sans toucher à la géométrie. Un reflow entraîne toujours un repaint, l’inverse est faux. Le reflow est l’opération la plus chère du rendu.
- Layout thrashing — alterner lectures de géométrie (
offsetWidth,getBoundingClientRect()) et écritures de style force un reflow synchrone à chaque lecture :
// ❌ Layout thrashing : N reflows forcés
boxes.forEach(box => {
const w = box.offsetWidth; // lecture → le navigateur DOIT
// recalculer le layout, invalidé
// par l'écriture du tour précédent
box.style.width = w / 2 + 'px'; // écriture → invalide le layout
});
// ✅ Corrigé : toutes les lectures, PUIS toutes les écritures
const widths = boxes.map(b => b.offsetWidth); // 1 layout encore valide
boxes.forEach((box, i) => {
box.style.width = widths[i] / 2 + 'px'; // 1 seul reflow, différé
}); // au prochain frame
💡 Réflexe à montrer — grouper lectures puis écritures, et caler les animations sur
requestAnimationFrame(une exécution par frame, juste avant le rendu). Pour prouver le problème : l’onglet Performance des DevTools marque les reflows forcés d’un triangle d’avertissement violet.
- Architecture multi-process — un browser process (UI, orchestration, accès disque/réseau), un renderer process par site (site isolation), un GPU process, des process réseau et utilitaires. Un onglet qui crashe n’emporte pas le navigateur, et deux sites ne partagent jamais le même espace mémoire (la réponse à Spectre).
- Sandbox — le renderer exécute du code non fiable (le web) : il n’a aucun accès direct au système de fichiers ni au réseau. Chaque opération sensible passe par IPC vers le browser process, qui contrôle. Un exploit dans le moteur de rendu reste enfermé dans le bac à sable.
- Stockage côté client :
| Cookies | localStorage | sessionStorage | |
|---|---|---|---|
| Envoyé au serveur | À chaque requête HTTP | Jamais | Jamais |
| Durée de vie | Expiration configurable | Persistant | Fermeture de l’onglet |
| Taille | ~4 Ko | ~5-10 Mo | ~5 Mo |
| Accès JavaScript | Oui, sauf HttpOnly | Oui | Oui |
| Portée | Domaine (+ path) | Origine | Origine + onglet |
- Same-origin policy, en une phrase : deux URLs partagent une origine si schéma + hôte + port sont identiques, et un document ne peut lire les données (DOM, storage, réponses) que de sa propre origine — CORS étant le mécanisme pour assouplir cette règle explicitement, côté serveur.
En entretien
« Quelle différence entre reflow et repaint ? » — Le reflow recalcule la géométrie (positions, tailles) et peut se propager à une grande partie de l’arbre ; le repaint redessine les pixels sans changer la géométrie. Le reflow inclut un repaint, jamais l’inverse. Déclencheurs : width ou ajout de nœud → reflow ; color → repaint ; transform/opacity → ni l’un ni l’autre (composite seul).
« defer vs async ? » — Les deux téléchargent sans bloquer le parser. async exécute dès que le script est prêt, ordre non garanti : scripts indépendants. defer exécute après le parsing, dans l’ordre du document, avant DOMContentLoaded : code qui touche au DOM. Bonus : type="module" est defer par défaut.
« Pourquoi animer avec transform plutôt que top/left ? » — top/left déclenchent layout + paint + composite à chaque frame ; transform est appliqué par le GPU à l’étape composite, sans reflow ni repaint. À 60 fps, c’est la différence entre une animation fluide et du jank.
« Où stocker un token d’authentification ? » — Pas dans localStorage : lisible par n’importe quel script de la page, donc volable à la première faille XSS. Le plus sûr : cookie HttpOnly + Secure + SameSite, invisible pour JavaScript. Montrer qu’on voit le compromis : le cookie part tout seul avec chaque requête → penser CSRF, contré par SameSite ou un token dédié.
« Pourquoi chaque onglet a-t-il son propre process ? » — Stabilité (un crash reste local à l’onglet), sécurité (sandbox du renderer + site isolation : deux origines ne partagent jamais leur mémoire), performance (vrai parallélisme sur plusieurs cœurs). Coût assumé : plus de RAM.
Pièges & idées reçues
⚠️ Piège vécu — lire
offsetWidthougetBoundingClientRect()dans une boucle qui écrit aussi des styles : chaque lecture force un reflow synchrone et le frame budget de 16 ms explose. Le code « marche », il est juste 50× trop lent — invisible sur votre machine de dev, flagrant sur un mobile milieu de gamme.
- « display:none et visibility:hidden, c’est pareil » — non :
display: nonesort l’élément du render tree (reflow quand il revient) ;visibility: hiddenconserve sa boîte (repaint seul). - « async est toujours mieux que defer » — non :
asyncpeut s’exécuter en plein parsing (et donc le bloquer à ce moment-là) et casse l’ordre entre scripts dépendants.deferest le défaut raisonnable. - « Le DOM, c’est le HTML » — le HTML est le texte source ; le DOM est l’arbre d’objets vivant, réparé par le parser (balises mal fermées) et mutable par JavaScript. Ce que montre l’inspecteur, c’est le DOM, pas le source.
- « Le CSS bloque le parsing du HTML » — imprécis : le CSS bloque le rendu et l’exécution des scripts, pas le parser HTML, qui continue à construire le DOM… tant qu’un script classique ne l’arrête pas.
- Oublier que
sessionStorageest par onglet : deux onglets du même site ne le partagent pas — source classique de bugs de « session perdue » en ouvrant un lien dans un nouvel onglet.
Pour aller plus loin
- MDN — Populating the page: how browsers work
- web.dev — Critical rendering path
- Inside look at modern web browser : l’architecture multi-process de Chrome illustrée, en 4 parties
- MDN — Same-origin policy
- Exercice : ouvrir l’onglet Performance des DevTools sur n’importe quel site, enregistrer 5 secondes de scroll, et retrouver layout, paint et composite dans la timeline
The essentials
The browser is not a black box that “displays HTML”: it’s a small operating system, with a rendering engine (Blink, WebKit, Gecko), a JavaScript engine (V8, JavaScriptCore, SpiderMonkey), a network stack and a multi-process architecture. The question “what happens between the server response and the pixels on screen?” is a front-end interview classic: it tests your understanding of rendering, performance and security all at once.
The full chain is called the critical rendering path: the minimal set of steps and blocking resources the browser must get through before the first render. Master it and you know why a page is slow — and where to act.
Three cost levels to remember right away:
| Change | Steps replayed | Cost |
|---|---|---|
width, margin, node insertion | Layout → Paint → Composite | High (reflow) |
color, background, visibility | Paint → Composite | Medium (repaint) |
transform, opacity | Composite only | Minimal (GPU) |
How it works
Six steps, always in the same order:
- HTML parsing → DOM — the parser reads the byte stream (parsing is incremental: it starts before the download finishes) and builds the DOM, the live object tree representing the document. A plain
<script>blocks the parser: it could calldocument.write(), so the browser must execute it before going on. - CSS parsing → CSSOM — stylesheets produce the CSSOM. CSS is render-blocking (no rendering without complete styles, otherwise a flash of unstyled content) and it also blocks script execution, since scripts might read computed styles.
- Render tree — DOM + CSSOM merged: only the visible nodes, with their computed styles.
display: noneis excluded;visibility: hiddenstays in (the element keeps its box). - Layout (reflow) — computing the exact geometry of every box: position and size, cascading down from the root.
- Paint — rasterization: every element becomes pixels, spread across one or more layers.
- Composite — the GPU assembles the layers in the right order. That’s why
transformandopacityare nearly free: they only touch this step.
HTML CSS
│ parsing │ parsing
▼ ▼
DOM + CSSOM
└────────────┬─────────────┘
▼
Render tree
▼
Layout (geometry → reflow)
▼
Paint (pixels → repaint)
▼
Composite (GPU, layers)
And what about scripts? Two attributes change everything:
async— downloads in parallel with parsing, executes as soon as it’s ready, possibly mid-parsing, in no guaranteed order. For independent scripts (analytics, ads).defer— downloads in parallel, executes after parsing finishes, in document order, right beforeDOMContentLoaded. The sane default for application code (and the behavior oftype="module").
🎤 In an interview — “what happens between the HTML response and the display?”: walk through the six-step pipeline (DOM, CSSOM, render tree, layout, paint, composite), point out what blocks what (plain script → parser; CSS → rendering and scripts), and close on the critical rendering path: fewer blocking resources = earlier first render. One minute, structured, unbeatable.
Key concepts to master
- Reflow vs repaint — reflow recomputes geometry and propagates (resizing a parent repositions its children, sometimes the whole tree); repaint redraws pixels without touching geometry. A reflow always triggers a repaint, never the other way around. Reflow is the most expensive operation in rendering.
- Layout thrashing — alternating geometry reads (
offsetWidth,getBoundingClientRect()) and style writes forces a synchronous reflow on every read:
// ❌ Layout thrashing: N forced reflows
boxes.forEach(box => {
const w = box.offsetWidth; // read → the browser MUST
// recompute layout, invalidated
// by the previous iteration's write
box.style.width = w / 2 + 'px'; // write → invalidates layout
});
// ✅ Fixed: all the reads, THEN all the writes
const widths = boxes.map(b => b.offsetWidth); // 1 still-valid layout
boxes.forEach((box, i) => {
box.style.width = widths[i] / 2 + 'px'; // 1 single reflow,
}); // deferred to next frame
💡 Reflex to show — batch reads then writes, and drive animations with
requestAnimationFrame(one execution per frame, right before rendering). To prove the problem: the DevTools Performance panel flags forced reflows with a purple warning triangle.
- Multi-process architecture — one browser process (UI, orchestration, disk/network access), one renderer process per site (site isolation), a GPU process, plus network and utility processes. A crashing tab doesn’t take the browser down, and two sites never share the same memory space (the answer to Spectre).
- Sandbox — the renderer runs untrusted code (the web): it has no direct access to the filesystem or the network. Every sensitive operation goes through IPC to the browser process, which checks it. An exploit in the rendering engine stays locked in the sandbox.
- Client-side storage:
| Cookies | localStorage | sessionStorage | |
|---|---|---|---|
| Sent to the server | With every HTTP request | Never | Never |
| Lifetime | Configurable expiry | Persistent | Tab close |
| Size | ~4 KB | ~5-10 MB | ~5 MB |
| JavaScript access | Yes, unless HttpOnly | Yes | Yes |
| Scope | Domain (+ path) | Origin | Origin + tab |
- Same-origin policy, in one sentence: two URLs share an origin if scheme + host + port are identical, and a document can only read data (DOM, storage, responses) from its own origin — CORS being the mechanism to relax that rule explicitly, server-side.
In an interview
“What’s the difference between reflow and repaint?” — Reflow recomputes geometry (positions, sizes) and can propagate through much of the tree; repaint redraws pixels without changing geometry. Reflow includes a repaint, never the other way around. Triggers: width or node insertion → reflow; color → repaint; transform/opacity → neither (composite only).
“defer vs async?” — Both download without blocking the parser. async executes as soon as the script is ready, order not guaranteed: independent scripts. defer executes after parsing, in document order, before DOMContentLoaded: code that touches the DOM. Bonus: type="module" is defer by default.
“Why animate with transform rather than top/left?” — top/left trigger layout + paint + composite on every frame; transform is applied by the GPU at the composite step, with no reflow or repaint. At 60 fps, that’s the difference between a smooth animation and jank.
“Where do you store an authentication token?” — Not in localStorage: readable by any script on the page, so stealable through the first XSS hole. Safest: an HttpOnly + Secure + SameSite cookie, invisible to JavaScript. Show you see the trade-off: the cookie travels automatically with every request → think CSRF, countered by SameSite or a dedicated token.
“Why does each tab get its own process?” — Stability (a crash stays local to the tab), security (renderer sandbox + site isolation: two origins never share memory), performance (real parallelism across cores). The accepted cost: more RAM.
Pitfalls & misconceptions
⚠️ Real-world trap — reading
offsetWidthorgetBoundingClientRect()inside a loop that also writes styles: every read forces a synchronous reflow and the 16 ms frame budget explodes. The code “works”, it’s just 50× too slow — invisible on your dev machine, glaring on a mid-range phone.
- “display:none and visibility:hidden are the same” — no:
display: noneremoves the element from the render tree (reflow when it comes back);visibility: hiddenkeeps its box (repaint only). - “async is always better than defer” — no:
asynccan execute mid-parsing (blocking it at that moment) and breaks ordering between dependent scripts.deferis the reasonable default. - “The DOM is the HTML” — HTML is the source text; the DOM is the live object tree, repaired by the parser (unclosed tags) and mutable by JavaScript. What the inspector shows is the DOM, not the source.
- “CSS blocks HTML parsing” — imprecise: CSS blocks rendering and script execution, not the HTML parser, which keeps building the DOM… until a plain script stops it.
- Forgetting that
sessionStorageis per tab: two tabs on the same site don’t share it — a classic source of “lost session” bugs when opening a link in a new tab.
Going further
- MDN — Populating the page: how browsers work
- web.dev — Critical rendering path
- Inside look at modern web browser: Chrome’s multi-process architecture illustrated, in 4 parts
- MDN — Same-origin policy
- Exercise: open the DevTools Performance panel on any site, record 5 seconds of scrolling, and find layout, paint and composite in the timeline