How the Browser Renders a Web Page
Understand the browser rendering pipeline — DNS, HTML parsing, DOM, CSSOM, render tree, layout, paint, and compositing. Essential knowledge for every frontend developer.
How the Browser Renders a Web Page
When you type a URL and press Enter, a fascinating chain of events happens in milliseconds. Understanding this pipeline helps you write faster websites and debug performance issues.
The Journey: URL to Pixels
Step 1: DNS Resolution
The browser needs to find the server's IP address. It checks:
- Browser cache
- OS cache
- Router cache
- ISP's DNS server
- Root DNS servers (if needed)
This lookup usually takes 20-120ms. DNS prefetching (<link rel="dns-prefetch">) can eliminate this delay for links your page will navigate to.
Step 2: TCP Connection + TLS
The browser establishes a connection with the server:
- TCP handshake — 3-way: SYN → SYN-ACK → ACK
- TLS handshake — for HTTPS, adds encryption negotiation
This takes another 50-200ms. HTTP/2 and HTTP/3 reduce this with connection reuse and QUIC protocol.
Step 3: HTTP Request/Response
The browser sends a GET request. The server responds with HTML. The browser starts processing bytes as they arrive — it doesn't wait for the complete response.
Building the DOM
The browser parses HTML from top to bottom, creating the DOM (Document Object Model) — a tree structure representing every element:
Document
├── html
│ ├── head
│ │ ├── title
│ │ └── link (stylesheet)
│ └── body
│ ├── header
│ │ └── nav
│ ├── main
│ │ ├── h1
│ │ └── p
│ └── footer
What Blocks DOM Construction?
<script>tags — The parser STOPS to download and execute JavaScript. This is why scripts go at the bottom or usedefer/async.defer— downloads in parallel, executes after DOM is completeasync— downloads in parallel, executes immediately when ready (can interrupt parsing)
- CSS does NOT block DOM construction — but it blocks rendering (explained below)
Building the CSSOM
While building the DOM, the browser also processes all CSS into the CSSOM (CSS Object Model). The CSSOM contains every style rule applied to every element, including:
- Your stylesheets
- Browser default styles
- Inline styles
CSS is render-blocking. The browser waits for ALL CSS to be loaded and parsed before rendering anything. This is why:
- Put
<link rel="stylesheet">in the<head>— so it loads early - Minimize CSS file size — every byte delays first render
- Consider critical CSS inlining for above-the-fold content
The Render Tree
The DOM and CSSOM combine into the Render Tree — only elements that will actually be painted:
- Elements with
display: noneare excluded - Pseudo-elements (
::before,::after) are included - The
<head>section is excluded
Render Tree
├── body (bg: white, font: 16px)
│ ├── header (h: 64px, bg: #1a1a2e)
│ │ └── nav (display: flex)
│ ├── main (padding: 24px)
│ │ ├── h1 (font-size: 2rem, font-weight: bold)
│ │ └── p (color: #333, line-height: 1.6)
│ └── footer (h: 48px, bg: #f5f5f5)
Layout (Reflow)
The browser calculates the exact position and size of every element in the render tree. This process is called Layout or Reflow.
Layout computes:
- Width and height of each element
- Position (x, y coordinates)
- Margin, padding, border calculations
- How elements affect each other's positions
Reflow is expensive. Changing one element's size can cascade changes through the entire tree.
What Triggers Reflow?
- Adding/removing elements
- Changing width, height, padding, margin
- Changing font size
window.resize- Reading layout properties (
offsetWidth,clientHeight,getBoundingClientRect())
Paint
After layout, the browser fills in pixels — colors, backgrounds, text, images, borders, shadows. This happens in layers.
What Triggers Repaint?
- Changing
color,background,border-color - Changing
visibility - Changing
box-shadow
Repaint is cheaper than reflow because it doesn't recalculate positions.
Compositing
Modern browsers paint content onto layers and composite them together. Elements that animate or change frequently can get their own layer, which makes updates much faster since only that layer needs to be repainted.
Properties that trigger layer promotion:
transformopacitywill-changeposition: fixed
This is why transform: translateX(100px) is faster than left: 100px for animations — transform only requires compositing, not layout + paint.
Performance Optimization Tips
1. Minimize Reflows
// Bad — triggers reflow on each read
for (let i = 0; i < 100; i++) {
el.style.width = el.offsetWidth + 10 + "px"; // read + write in loop
}
// Good — batch reads and writes
const width = el.offsetWidth; // single read
for (let i = 0; i < 100; i++) {
el.style.width = width + 10 * i + "px"; // writes only
}
2. Use transform for Animations
/* Expensive — triggers layout on every frame */
.animate { left: 0; transition: left 0.3s; }
.animate:hover { left: 100px; }
/* Cheap — only compositing */
.animate { transform: translateX(0); transition: transform 0.3s; }
.animate:hover { transform: translateX(100px); }
3. Use will-change Sparingly
.element-that-will-animate {
will-change: transform;
}
This hints the browser to promote the element to its own layer. Don't overuse it — each layer uses memory.
4. Defer Non-Critical Resources
<link rel="stylesheet" href="critical.css">
<link rel="stylesheet" href="below-fold.css" media="print" onload="this.media='all'">
<script src="app.js" defer></script>
The Complete Pipeline
URL → DNS → TCP/TLS → HTTP → HTML bytes
↓
DOM Tree ──────┐
├── Render Tree → Layout → Paint → Composite → Pixels!
CSSOM Tree ────┘
Key Takeaways
- CSS is render-blocking, JS is parser-blocking — load both strategically
- Use
deferfor scripts and load CSS early - Reflow (layout changes) is expensive — minimize it
- Use
transformandopacityfor animations — they skip layout and paint - The render pipeline: DOM + CSSOM → Render Tree → Layout → Paint → Composite
- Every layout "read" (like
offsetWidth) forces the browser to recalculate — batch your reads
🚀 Practice What You Learned
Apply these concepts with hands-on coding challenges: