Suggest an editImprove this articleRefine the answer for “How browser works when entering request and rendering stages”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Browser rendering pipeline** - the full sequence from URL input to pixels on screen. 1. DNS lookup (cache → hosts → recursive DNS server) 2. TCP handshake (SYN, SYN-ACK, ACK) 3. TLS handshake (HTTPS only) 4. HTTP GET request and server response 5. HTML parsing → DOM, CSS parsing → CSSOM, JS execution 6. Render tree (DOM + CSSOM, hidden elements excluded) 7. Layout (positions and sizes), Paint (pixels), Composite (GPU layers) **Key point:** JS blocks parsing without `async`/`defer`. `transform` animations run on the compositor thread and never trigger layout or paint.Shown above the full answer for quick recall.Answer (EN)Image**Browser rendering pipeline** - the sequence of steps from URL input to pixels on screen, split into two phases: network and rendering. ## Theory ### TL;DR - Network phase: DNS lookup, TCP handshake, TLS (HTTPS only), HTTP request, server response - Rendering phase: HTML parsing (DOM), CSS parsing (CSSOM), JS execution, render tree, layout, paint, composite - JS without `async` or `defer` blocks HTML parsing completely - Animating `transform` skips layout and paint - the compositor handles it on the GPU - Reading a layout property right after writing a style forces synchronous layout - avoid this in loops ### Pipeline overview ```javascript // From Enter key to pixels - every step in order: // 1. DNS: example.com → 93.184.216.34 // 2. TCP: SYN → SYN-ACK → ACK // 3. TLS: certificate + cipher negotiation (HTTPS only) // 4. HTTP: GET /index.html HTTP/1.1 // 5. Response: server sends HTML bytes (chunked or full) // 6. DOM: parser builds node tree from HTML incrementally // 7. CSSOM: parser builds style tree from CSS (must complete fully) // 8. JS: executes, may modify DOM or CSSOM // 9. Render: DOM + CSSOM = render tree (visible nodes only) // 10. Layout: calculate exact position and size of each node // 11. Paint: rasterize pixels per layer (back to front) // 12. Composite: GPU merges layers → final frame on screen ``` Each step produces output that the next one needs. If one stalls, everything after it waits. ### DNS resolution The browser needs an IP address before it can open a connection. It checks four places in order: 1. Its own DNS cache (browser-level, TTL is often very short) 2. The OS `hosts` file (static overrides, handy in local dev) 3. The OS resolver cache 4. A recursive DNS server - your ISP's or a public one like 8.8.8.8 The recursive server, if it has no cached answer, walks the DNS hierarchy: root nameservers → TLD nameserver (`.com`, `.io`) → authoritative nameserver for the domain. On a warm cache, this takes under 1ms. On a cold lookup, add 20-120ms depending on geography. ### TCP and TLS handshake TCP requires three messages before any data moves: - **SYN**: client opens the conversation - **SYN-ACK**: server accepts - **ACK**: client confirms, connection is ready For HTTPS, a TLS handshake follows. The server sends its certificate, both sides agree on a cipher suite, and they derive session keys. HTTP/2 runs on TLS and multiplexes all resource requests over one connection, removing the per-request handshake cost. HTTP/3 uses QUIC and combines TCP and TLS setup into a single round trip. ### HTTP request and server response The browser sends a GET request with headers: `Host`, `User-Agent`, `Accept-Encoding`, and any cookies for that domain. The server replies with a status code and a body. A 200 with HTML starts the rendering pipeline. A 301 or 302 restarts the whole process with a new URL. Chunked transfer encoding lets the browser start parsing HTML before the full response arrives. This is why pages sometimes render progressively - the browser does not wait. ### Building DOM and CSSOM HTML parsing is incremental. The browser tokenizes the byte stream, creates nodes, and attaches them to the tree as it reads. It does not wait for the full document. CSS is different. The CSSOM has to be complete before the browser uses any of it, because later rules can override earlier ones. A large unoptimized stylesheet blocks the render tree from forming. This is one reason splitting CSS by media query or deferring non-critical styles matters. ### JavaScript and the render-blocking problem A `<script>` tag without attributes stops HTML parsing entirely. The browser fetches the script, executes it, then resumes. This exists because JS can call `document.write()` or modify the DOM mid-parse. `async` downloads the script in parallel but still pauses the parser at execution time. `defer` downloads in parallel and executes only after the HTML is fully parsed, in document order. For most scripts, `defer` is the right choice. I have watched teams spend two hours debugging load order issues because they put `async` on scripts that depended on each other. Execution order with `async` is not guaranteed. ### Render tree The render tree is not the DOM. It combines each visible DOM node with its computed styles from the CSSOM. `<head>`, `<script>`, and any element with `display: none` are excluded. An element with `visibility: hidden` stays in the tree - it still takes up space and affects layout. ### Layout (reflow) Layout computes the exact box for every render tree node: position, width, height, margins, padding - all relative to the viewport. This is where the CSS box model actually runs. Layout is expensive. Changing the width of a parent can trigger recalculation for all its children. Reading a layout property (`offsetHeight`, `getBoundingClientRect`) right after writing a style property forces the browser to flush its pending layout queue synchronously. This is called layout thrashing, and it kills frame rate fast. ### Paint and composite Paint converts each layer into actual pixels: colors, text glyphs, borders, shadows. Layers are painted back to front (painter's algorithm). Each layer is rasterized independently. Composite happens on the GPU. Elements with `transform`, `opacity`, `will-change`, or `position: fixed` often get their own compositor layer. The GPU merges all layers and sends the final frame to the screen. This is why `transform` animations are smooth: they never trigger layout or paint, only composite. ### Common mistakes **Render-blocking scripts in `<head>`** ```html <!-- blocks parsing while fetching and executing --> <head> <script src="app.js"></script> </head> <!-- fetches in parallel, executes after parse - no blocking --> <head> <script src="app.js" defer></script> </head> ``` **Layout thrashing in a loop** ```javascript // Bad: read forces layout flush, write marks it dirty again elements.forEach(el => { const h = el.offsetHeight; // read: synchronous layout el.style.height = (h + 10) + 'px'; // write: layout dirty again }); // Good: batch reads first, then batch writes const heights = elements.map(el => el.offsetHeight); // one layout elements.forEach((el, i) => { el.style.height = (heights[i] + 10) + 'px'; }); ``` **Animating geometry properties** ```css /* Triggers layout + paint + composite every frame */ .slow-animation { transition: width 0.3s, left 0.3s; } /* Triggers composite only - no layout, no paint */ .fast-animation { transition: transform 0.3s, opacity 0.3s; } ``` **Missing `defer` on third-party scripts** Adding analytics or chat widgets without `defer` can add hundreds of milliseconds to Time to Interactive on slow connections. These scripts almost never need to run before the HTML is parsed. ### Real-world usage - Chrome DevTools Performance tab: yellow = JS execution, purple = layout, green = paint, thin green bars = composite - Lighthouse "Eliminate render-blocking resources" flag catches stylesheets and scripts delaying First Contentful Paint - React and Vue minimize the number of DOM mutations that trigger layout and paint - they do not bypass any of these stages - `will-change: transform` on an element promotes it to its own compositor layer before an animation starts, avoiding the mid-animation promotion cost ### Follow-up questions **Q:** What is the critical rendering path? **A:** The sequence DOM, CSSOM, render tree, layout, paint. Optimizing it means reducing the number of render-blocking resources and the byte size of each resource that participates in this path. **Q:** What is the difference between reflow and repaint? **A:** Reflow recalculates geometry and is triggered by size, position, or font changes. Repaint redraws pixels without changing geometry, triggered by color or background changes. Reflow always causes repaint. Repaint does not cause reflow. **Q:** Why does `visibility: hidden` behave differently from `display: none` in terms of layout? **A:** `display: none` removes the element from the render tree entirely - it takes no space. `visibility: hidden` keeps it in the tree with full layout impact, so the element still occupies space and affects surrounding elements. **Q:** How does HTTP/2 change the network phase? **A:** HTTP/2 multiplexes all requests over one TCP connection, removing the per-resource handshake cost. It also compresses headers with HPACK and supports server push. HTTP/3 goes further with QUIC, eliminating TCP head-of-line blocking. **Q:** Senior scenario: you read `el.offsetWidth` and then set `el.style.width` inside a `requestAnimationFrame` callback, repeating this on every frame. What happens and why? **A:** Reading `offsetWidth` forces the browser to synchronously flush its pending layout queue to return a fresh value. Writing to `style.width` marks layout dirty again. On the next frame, the read forces another synchronous layout. This is layout thrashing inside rAF - it fires a forced layout on every single frame and is one of the most common causes of dropped frames. ## Examples ### Tracing a page load in Chrome DevTools Open DevTools, go to the Network tab, click any HTML request, and open the Timing panel. You will see each network stage broken out: - "DNS Lookup": time spent in DNS resolution - "Initial Connection": TCP handshake duration - "SSL": TLS negotiation (HTTPS only) - "Time to First Byte": server processing plus response start - "Content Download": time receiving the HTML bytes Switch to the Performance tab and record a reload. The flame chart on the Main thread maps directly to the rendering pipeline: purple blocks are layout, green are paint, yellow are script execution. Composite shows up as thin green bars on the Compositor thread. ### Script loading strategies compared ```html <!-- Default: parser blocks during fetch AND execute --> <script src="heavy-lib.js"></script> <!-- async: fetches in parallel, executes immediately when ready --> <!-- order not guaranteed, use for independent scripts only --> <script src="analytics.js" async></script> <!-- defer: fetches in parallel, executes after HTML fully parsed --> <!-- order preserved, safe for app code that reads DOM --> <script src="app.js" defer></script> <script src="components.js" defer></script> <!-- components.js always runs after app.js --> ``` Rule of thumb: `defer` for anything that touches the DOM, `async` for truly independent scripts where order does not matter (error trackers, analytics beacons). ### Compositor layers and animation performance ```javascript const box = document.querySelector('.box'); // Slow: triggers full pipeline (layout + paint + composite) every frame function animateSlow() { let x = 0; function tick() { x += 2; box.style.left = x + 'px'; // layout dirty → reflow → repaint requestAnimationFrame(tick); } requestAnimationFrame(tick); } // Fast: runs on compositor thread, zero layout or paint function animateFast() { let x = 0; function tick() { x += 2; box.style.transform = `translateX(${x}px)`; // compositor only requestAnimationFrame(tick); } requestAnimationFrame(tick); } ``` Add `will-change: transform` to the element's CSS to promote it to its own compositor layer before the animation starts. This avoids the cost of layer promotion happening mid-animation on the first frame.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.