Browser Rendering Pipeline
When a browser loads a webpage, a lot happens between receiving the HTML and putting pixels on screen. That sequence of steps is the rendering pipeline.
Understanding it helps you write more performant frontend code and avoid common bottlenecks.
- Parse HTML → DOM Tree
- Parse CSS → CSSOM Tree
- Combine → Render Tree
- Layout
- Paint
- Compositing
- Performance: Reflow vs. Repaint vs. Compositing
1. Parse HTML → DOM Tree
The browser parses the HTML from top to bottom, converting tags into the DOM Tree.
When the parser hits a <script> tag, it pauses by default — the script has to download and execute before parsing continues. That's why scripts are typically placed at the bottom of <body>, or use defer / async.
<!-- blocks parsing -->
<script src="app.js"></script>
<!-- downloads in background, executes after HTML is parsed -->
<script src="app.js" defer></script>
<!-- downloads in background, executes as soon as it's ready -->
<script src="app.js" async></script>2. Parse CSS → CSSOM Tree
While parsing HTML, the browser also parses CSS and builds the CSSOM Tree.
The browser won't render anything until the CSSOM is complete — making CSS a render-blocking resource.
Placing <link> tags in <head> lets CSS start downloading as early as possible, minimizing how long rendering is blocked.
3. Combine → Render Tree
The DOM Tree and CSSOM Tree are combined into the Render Tree.
The Render Tree only includes visible nodes:
- Elements with
display: noneare excluded — they take up no space - Elements with
visibility: hiddenare included — they're invisible but still occupy space - Non-visual elements like
<head>and<script>are excluded
4. Layout
In the Layout phase (also called Reflow), the browser calculates the position and size of every element in the Render Tree.
This determines:
- Each element's width and height
- Where each element sits on the page
- How elements relate to each other
Layout is expensive because changing one element's dimensions can cascade and affect the positioning of others.
5. Paint
In the Paint phase (also called Repaint), the browser fills in the pixels for each element:
- Background colors
- Text
- Borders
- Shadows
The browser divides the page into layers. Elements with complex styles — like those using transform or opacity — get their own layer.
6. Compositing
In the Compositing phase, the browser takes all the layers and combines them in the correct order to produce the final frame on screen.
Elements on their own compositor layer (achieved with transform: translateZ(0) or will-change: transform) can be processed entirely on the GPU, skipping Layout and Paint entirely — which makes them the most performant to animate.
Performance: Reflow vs. Repaint vs. Compositing
Reflow (most expensive)
These operations trigger a full Layout recalculation:
- Changing an element's dimensions (
width,height,padding,margin) - Adding or removing elements
- Changing font size
- Reading certain properties (
offsetWidth,offsetHeight,getBoundingClientRect())
Reading layout properties forces the browser to calculate Layout immediately, even if it hasn't rendered yet. This is called forced synchronous layout.
Repaint (moderate cost)
Changing visual styles that don't affect size or position only triggers a Repaint:
colorbackground-colorvisibilitybox-shadow
Compositing only (cheapest)
These only affect the GPU compositing step — no Layout or Paint:
transformopacity
Practical Tips
Batch style changes
// bad: each change may trigger a Reflow
element.style.width = '100px';
element.style.height = '200px';
element.style.margin = '10px';
// good: one change, one Reflow
element.className = 'new-style';
// or
element.style.cssText = 'width: 100px; height: 200px; margin: 10px;';Don't mix reads and writes in a loop
// bad: reading offsetWidth inside the loop forces repeated Reflows
for (let i = 0; i < items.length; i++) {
items[i].style.width = container.offsetWidth + 'px';
}
// good: read once, then write
const containerWidth = container.offsetWidth;
for (let i = 0; i < items.length; i++) {
items[i].style.width = containerWidth + 'px';
}Use transform instead of position properties for movement
// triggers Reflow
element.style.left = '100px';
// triggers Compositing only — much cheaper
element.style.transform = 'translateX(100px)';Conclusion
The rendering pipeline in order:
Performance cost from highest to lowest: Reflow > Repaint > Compositing
The core principle for performant frontend code: avoid triggering Reflow wherever possible, and prefer properties that only trigger Compositing — transform and opacity.