The Browser
What the Browser Actually Does
The browser is not just a window that displays web pages. It is a complex runtime environment that parses markup, executes code, manages network requests, and paints pixels. Understanding what happens between receiving HTML and seeing a rendered page is essential knowledge.
The high-level pipeline:
- Parse HTML and build the DOM (Document Object Model)
- Parse CSS and build the CSSOM (CSS Object Model)
- Combine DOM and CSSOM into the Render Tree
- Calculate Layout (where each element goes and how big it is)
- Paint pixels for each element
- Composite layers into the final image on screen
Parsing HTML & Building the DOM
The browser reads the HTML byte stream and converts it into a tree of nodes.
<!DOCTYPE html>
<html>
<head>
<title>My Page</title>
</head>
<body>
<h1>Hello</h1>
<p>World</p>
</body>
</html>
This produces a DOM tree:
Document
├── html
│ ├── head
│ │ └── title
│ │ └── "My Page"
│ └── body
│ ├── h1
│ │ └── "Hello"
│ └── p
│ └── "World"
The parser works incrementally -- it does not wait for the entire document to arrive. It processes tokens as they stream in. This is why you see content appearing progressively on slow connections.
When the Parser Gets Blocked
The parser stops when it encounters a <script> tag without async or defer. The browser must download, parse, and execute the script before continuing with the HTML, because the script might call document.write() and modify the document.
<!-- This blocks parsing -->
<script src="app.js"></script>
<!-- This does NOT block parsing -->
<script src="app.js" defer></script>
<!-- This downloads in parallel, executes as soon as ready -->
<script src="analytics.js" async></script>
Rule of thumb: Put scripts at the end of <body>, or use defer. Use async only for scripts that do not depend on the DOM or other scripts.
Parsing CSS & Building the CSSOM
While the HTML parser builds the DOM, the browser also parses CSS into the CSSOM. Unlike the DOM, the CSSOM cannot be built incrementally -- the browser must parse all CSS before it can determine which styles apply to which elements (because later rules can override earlier ones).
This is why CSS is render-blocking. The browser will not render anything until it has built the CSSOM.
<!-- This blocks rendering until the stylesheet is downloaded and parsed -->
<link rel="stylesheet" href="styles.css">
<!-- This only blocks rendering for print media -->
<link rel="stylesheet" href="print.css" media="print">
Use the media attribute to mark stylesheets that are only needed for specific contexts. The browser still downloads them, but it does not block rendering for non-matching media.
The Render Tree
The render tree combines the DOM and CSSOM. It includes only the nodes that are actually visible:
- Elements with
display: noneare excluded - The
<head>element is excluded - Pseudo-elements (like
::before) are included
DOM CSSOM Render Tree
├── html h1 { color: blue } ├── body
│ ├── head p { font-size: 16px }│ ├── h1 (color: blue)
│ └── body .hidden { display: │ └── p (font-size: 16px)
│ ├── h1 none }
│ ├── p
│ └── div.hidden (div.hidden excluded)
Layout (Reflow)
With the render tree built, the browser calculates the exact position and size of every element. This is called layout (or reflow).
Layout starts at the root and works down the tree. Each element's size depends on:
- Its content
- Its CSS properties (width, height, padding, margin, border)
- Its parent's size
- The viewport size
Layout is expensive. Changing one element's size can trigger layout recalculation for its siblings, parent, and children. This is why layout thrashing is a major performance problem.
// Layout thrashing: read, write, read, write...
const height = element.offsetHeight; // forces layout
element.style.height = height + 10 + 'px'; // invalidates layout
const width = element.offsetWidth; // forces layout again
element.style.width = width + 10 + 'px'; // invalidates layout again
Batch your reads and writes to avoid this:
// Better: batch reads, then batch writes
const height = element.offsetHeight;
const width = element.offsetWidth;
element.style.height = height + 10 + 'px';
element.style.width = width + 10 + 'px';
Paint & Composite
After layout, the browser paints each element into layers. This means filling in pixels: text, colors, borders, shadows, images.
Then the compositor takes those layers and combines them into the final image. Some CSS properties (like transform and opacity) can be handled entirely by the compositor without triggering layout or paint, which is why they are cheap to animate.
/* Cheap to animate (compositor only) */
.animate-good {
transition: transform 0.3s, opacity 0.3s;
}
/* Expensive to animate (triggers layout + paint) */
.animate-bad {
transition: width 0.3s, height 0.3s;
}
The Critical Rendering Path
The critical rendering path is the sequence of steps the browser must complete before it can render the first pixel:
HTML → DOM
↘
Render Tree → Layout → Paint
↗
CSS → CSSOM
To get the fastest first paint:
- Minimize the size of critical resources (HTML, CSS needed for above-the-fold content)
- Minimize the number of critical resources (inline critical CSS, defer non-critical CSS)
- Minimize the critical path length (reduce round trips)
<!-- Inline critical CSS for fastest first paint -->
<style>
body { font-family: system-ui; margin: 0; }
.hero { padding: 2rem; background: #1a1a2e; color: white; }
</style>
<!-- Load the rest asynchronously -->
<link rel="preload" href="full-styles.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
DevTools: Your Window into the Browser
Elements Tab
Inspect and modify the DOM and CSS in real time. Right-click any element on the page and select "Inspect".
- View the DOM tree and computed styles
- Edit HTML and CSS live
- See which CSS rules apply and which are overridden
- Check the box model (content, padding, border, margin)
Console Tab
Execute JavaScript, view errors, and log output.
// Quick DOM queries
document.querySelectorAll('img:not([alt])'); // Find images missing alt text
document.querySelectorAll('a[href="#"]'); // Find placeholder links
// Performance quick checks
performance.timing.domContentLoadedEventEnd -
performance.timing.navigationStart; // Time to DOMContentLoaded
Network Tab
See every request the browser makes: HTML, CSS, JS, images, fonts, API calls.
Key things to look for:
- Waterfall: Shows when each request starts and how long it takes
- Size: Transferred size vs actual size (compression matters)
- Time: DNS, connection, TLS, waiting (TTFB), download
- Initiator: What triggered this request
Filter by type (JS, CSS, Img, Font, XHR) to focus on what matters.
Performance Tab
Record a trace of everything the browser does: parsing, scripting, layout, paint, composite.
Key metrics:
- First Contentful Paint (FCP): When the first text or image appears
- Largest Contentful Paint (LCP): When the largest content element is visible
- Total Blocking Time (TBT): How long the main thread is blocked
- Cumulative Layout Shift (CLS): How much the page layout shifts unexpectedly
Look for long tasks (yellow bars over 50ms) that block the main thread.
Common Pitfalls
- Putting CSS at the bottom: CSS is render-blocking by design. The browser needs it before it can render. Put
<link>tags in<head>so the download starts immediately. - Putting scripts in
<head>without defer: This blocks HTML parsing. Usedeferfor scripts that need the DOM,asyncfor independent scripts. - Loading too many fonts: Each font file blocks text rendering (FOIT -- Flash of Invisible Text). Use
font-display: swapand limit font weights/styles. - Not understanding reflow triggers: Reading layout properties (
offsetHeight,getBoundingClientRect) forces the browser to calculate layout. Doing this in a loop with style changes causes layout thrashing. - Ignoring the Network tab: Many performance problems are not code problems -- they are network problems. Large images, uncompressed assets, and too many requests.
- Testing only on fast connections: Use Network throttling in DevTools (Fast 3G, Slow 3G) to simulate real-world conditions.
Key Takeaways
- The browser builds the DOM from HTML and the CSSOM from CSS, then combines them into a render tree
- CSS is render-blocking; JavaScript is parser-blocking (without async/defer)
- The critical rendering path determines how fast the first pixel appears on screen
- Layout (reflow) is expensive; avoid triggering it unnecessarily
- Animate
transformandopacityinstead ofwidth,height, ortop - DevTools is not optional -- Elements, Console, Network, and Performance tabs are essential daily tools
- The browser does a remarkable amount of work; understanding that work helps you write faster pages