Critical Rendering Path (CRP) in JavaScript

  • Let me explain what happens behind the scenes when you load a webpage in your browser.

What is Critical Rendering Path?

  • Simple Definition: The Critical Rendering Path is the sequence of steps the browser takes to convert HTML, CSS, and JavaScript into pixels on the screen.

Real-world Analogy: Imagine you're building a house:

  1. First, you create the blueprint (HTML parsing)
  2. Then, you decide the colors and interior design (CSS parsing)
  3. You build the structure (DOM + CSSOM)
  4. You add furniture and decorations (Render Tree)
  5. You measure where everything goes (Layout)
  6. Finally, you paint and finish everything (Paint)

The browser does something similar to display your webpage!


The 6 Steps of Critical Rendering Path

Step 1: DOM Construction (Document Object Model)

What happens: Browser reads HTML and converts it into a tree structure called the DOM.


    <!DOCTYPE html>
    <html>

    <head>
        <title>My Page</title>
    </head>

    <body>
        <div class="container">
            <h1>Hello World</h1>
            <p>Welcome to my site</p>
        </div>
    </body>

    </html>

Browser converts this to:


    html
    └── head
        └── title
    └── body
        └── div.container
            ├── h1
            └── p

  • Important: Browser does this incrementally (line by line). It doesn't wait for the entire HTML to download.


Step 2: CSSOM Construction (CSS Object Model)

What happens: Browser reads CSS and creates a tree structure similar to DOM, called CSSOM.


    body {
        font-size: 16px;
    }

    .container {
        width: 100%;
        padding: 20px;
    }

    h1 {
        color: blue;
        font-size: 32px;
    }

    p {
        color: gray;
    }
 

Browser creates CSSOM:


    body (font-size: 16px)
    └── .container (width: 100%, padding: 20px)
        ├── h1 (color: blue, font-size: 32px, inherits font-size from body)
        └── p (color: gray, inherits font-size from body)

  • Critical Point: CSS is render-blocking. The browser won't render anything until it has processed all CSS. Why? Because CSS rules can override each other, so the browser needs the complete picture.


Step 3: Render Tree Construction

  • What happens: Browser combines DOM and CSSOM to create a Render Tree. This tree contains only the elements that will be visible on the screen.

Example:


    <div>
        <p>Visible paragraph</p>
        <p style="display: none;">Hidden paragraph</p>
        <span>Visible span</span>
    </div>

Render Tree only includes:


    div
    ├── p (Visible paragraph)
    └── span (Visible span)

  • The hidden paragraph is NOT in the Render Tree because it won't be displayed.

Elements excluded from Render Tree:

  • display: none elements
  • <head> and its children
  • <script> tags
  • <meta> tags

Step 4: Layout (Reflow)

  • What happens: Browser calculates the exact position and size of each element on the screen.
  • Real-world Analogy: You know what furniture you have (Render Tree), now you need to measure and decide where each piece goes in your room.


    <div style="width: 100%">
        <div style="width: 50%">
            <p>Text here</p>
        </div>
    </div>

Browser calculates:

  • If viewport is 1000px wide
  • Outer div = 1000px
  • Inner div = 500px (50% of 1000px)
  • Position of each element (x, y coordinates)

This process is also called "Reflow"


Step 5: Paint

  • What happens: Browser fills in pixels. It paints text, colors, images, borders, shadows - everything visual.

Order matters: Browser paints in layers:

  1. Background colors
  2. Background images
  3. Borders
  4. Children elements
  5. Outlines

Step 6: Compositing

  • What happens: If you have multiple layers (like elements with position: fixed or CSS transforms), the browser combines them in the correct order.


JavaScript's Role in CRP

  • JavaScript can block the entire rendering process. Let's understand how:

Problem 1: Parser-Blocking JavaScript


    <!DOCTYPE html>
    <html>

    <head>
        <title>My Page</title>
        <script src="large-script.js"></script> <!-- BLOCKS HERE -->
    </head>

    <body>
        <h1>Hello World</h1>
    </body>

    </html>

What happens:

  1. Browser starts parsing HTML
  2. Encounters <script> tag
  3. STOPS parsing HTML
  4. Downloads and executes JavaScript
  5. Only then continues parsing HTML

  • Why? Because JavaScript can modify the DOM (using document.write() or manipulating elements), so the browser must execute it before continuing.


Problem 2: JavaScript Waiting for CSS


    <!DOCTYPE html>
    <html>

    <head>
        <link rel="stylesheet" href="styles.css">
        <script src="app.js"></script>
    </head>

    <body>
        <h1>Hello World</h1>
    </body>

    </html>

What happens:

  1. Browser starts downloading CSS
  2. Encounters <script> tag
  3. WAITS for CSS to download and parse
  4. Then executes JavaScript
  5. Then continues parsing HTML

  • Why? Because JavaScript might read computed styles (like getComputedStyle()), so it needs complete CSS information.


Optimization Techniques

1. Async Attribute


    <script src="analytics.js" async></script>

What it does:

  • Downloads script in parallel with HTML parsing
  • Executes script as soon as it's downloaded (might interrupt parsing)
  • Good for scripts that don't depend on DOM or other scripts

Visual Timeline:


    HTML Parsing: ————————————————————————
    Script Download:    ————————
    Script Execute:           ⚡
    HTML Parsing continues: ————————————————


2. Defer Attribute


    <script src="main.js" defer></script>

What it does:

  • Downloads script in parallel with HTML parsing
  • Executes script after HTML parsing is complete
  • Scripts execute in order
  • Perfect for scripts that need complete DOM

Visual Timeline:


    HTML Parsing: ————————————————————————
    Script Download:    ————————
    Script Execute:                        ⚡


3. Placing Scripts at Bottom


    <!DOCTYPE html>
    <html>

    <head>
        <title>My Page</title>
    </head>

    <body>
        <h1>Hello World</h1>
        <p>Content here</p>

        <!-- Scripts at the end -->
        <script src="app.js"></script>
    </body>

    </html>

  • Benefit: HTML is parsed and rendered first, then JavaScript executes.


4. Critical CSS (Inline)


    <!DOCTYPE html>
    <html>

    <head>
        <style>
            /* Inline critical CSS for above-the-fold content */
            body {
                margin: 0;
                font-family: Arial;
            }

            .hero {
                height: 100vh;
                background: blue;
            }
        </style>

        <!-- Load non-critical CSS asynchronously -->
        <link rel="preload" href="styles.css" as="style" onload="this.rel='stylesheet'">
    </head>

    <body>
        <div class="hero">Welcome</div>
    </body>

    </html>


5. Minification

Before:


    function calculateTotal(price, quantity) {
        const tax = 0.1;
        const subtotal = price * quantity;
        const total = subtotal + (subtotal * tax);
        return total;
    }

After (minified):


    function calculateTotal(p, q) { const t = .1, s = p * q; return s + s * t }

Benefit: Smaller file size = faster download


Real-World Example: Optimized HTML Structure

❌ Bad (Slow Loading):


    <!DOCTYPE html>
    <html>

    <head>
        <link rel="stylesheet" href="styles.css">
        <link rel="stylesheet" href="animations.css">
        <link rel="stylesheet" href="fonts.css">
        <script src="jquery.js"></script>
        <script src="bootstrap.js"></script>
        <script src="app.js"></script>
    </head>

    <body>
        <h1>Welcome</h1>
    </body>

    </html>

Problems:

  • Multiple CSS files block rendering
  • JavaScript blocks parsing
  • No prioritization

✅ Good (Optimized):


    <!DOCTYPE html>
    <html>

    <head>
        <!-- Inline critical CSS -->
        <style>
            body {
                margin: 0;
                font-family: Arial;
            }

            h1 {
                font-size: 2em;
                color: #333;
            }
        </style>

        <!-- Preload important resources -->
        <link rel="preload" href="fonts/main.woff2" as="font" type="font/woff2" crossorigin>

        <!-- Load non-critical CSS asynchronously -->
        <link rel="preload" href="styles.css" as="style" onload="this.rel='stylesheet'">
    </head>

    <body>
        <h1>Welcome</h1>

        <!-- Scripts with defer at the end -->
        <script src="app.js" defer></script>
    </body>

    </html>

Benefits:
  • Critical styles load immediately
  • JavaScript doesn't block rendering
  • Fonts preload for faster text rendering

Measuring CRP Performance

Key Metrics:

1. First Contentful Paint (FCP)

  • When the first text or image appears
  • Target: Under 1.8 seconds

2. Largest Contentful Paint (LCP)

  • When the largest element appears
  • Target: Under 2.5 seconds

3. Time to Interactive (TTI)

  • When page becomes fully interactive
  • Target: Under 3.8 seconds

How to Measure:

Chrome DevTools:


    // In Console
    performance.getEntriesByType('navigation')[0]

Lighthouse:

  • Open DevTools → Lighthouse tab → Generate report

Practical JavaScript Optimization Example

❌ Bad: Blocking JavaScript


    <script>
        // Heavy computation blocking render
        function fibonacci(n) {
            if (n <= 1) return n;
            return fibonacci(n - 1) + fibonacci(n - 2);
        }

        const result = fibonacci(40); // Takes several seconds!
        console.log(result);
    </script>

    <h1>My Website</h1>

  • Problem: User sees blank screen while JavaScript executes.


✅ Good: Non-Blocking JavaScript


    <h1>My Website</h1>

    <script defer>
        // Run after page renders
        function fibonacci(n) {
            if (n <= 1) return n;
            return fibonacci(n - 1) + fibonacci(n - 2);
        }

        // Or use Web Workers for heavy computation
        setTimeout(() => {
            const result = fibonacci(40);
            console.log(result);
        }, 0);
    </script>

  • Benefit: Page renders immediately, heavy computation runs after.


Understanding Reflow and Repaint

Reflow (Expensive)

Triggered by changes to:

  • Element dimensions (width, height)
  • Position (top, left)
  • Display property
  • Font size
  • Window resize

    // ❌ Bad: Multiple reflows
    const element = document.getElementById('box');
    element.style.width = '100px'; // Reflow
    element.style.height = '100px'; // Reflow
    element.style.margin = '10px'; // Reflow
    // ✅ Good: Single reflow
    const element = document.getElementById('box');
    element.style.cssText = 'width: 100px; height: 100px; margin: 10px;';


Repaint (Less Expensive)

Triggered by changes to:

  • Color
  • Background color
  • Visibility
  • Outline

    element.style.backgroundColor = 'red'; // Only repaint, no reflow


Advanced: Layer Promotion

Some CSS properties create new layers and avoid reflow/repaint:


    .animated-box {
        /* These properties use GPU and don't trigger reflow */
        transform: translateX(100px);
        opacity: 0.5;
        will-change: transform;
        /* Hint to browser to create layer */
    }

Animating with transform is faster than animating position:


    /* ❌ Slow: Causes reflow */
    .box {
        animation: slide 1s;
    }

    @keyframes slide {
        from {
            left: 0;
        }

        to {
            left: 100px;
        }
    }

    /* ✅ Fast: Uses GPU, no reflow */
    .box {
        animation: slide 1s;
    }

    @keyframes slide {
        from {
            transform: translateX(0);
        }

        to {
            transform: translateX(100px);
        }
    }


Summary - The Complete Flow


1. HTML Download
   ↓
2. HTML Parsing → DOM Construction
   ↓ (if CSS found)
3. CSS Download → CSSOM Construction
   ↓ (CSS blocks rendering)
4. Render Tree = DOM + CSSOM
   ↓
5. Layout (Calculate positions/sizes)
   ↓
6. Paint (Fill in pixels)
   ↓
7. Composite (Combine layers)
   ↓
8. Display on Screen ✅

JavaScript can interrupt this flow at any step!


Key Takeaways

  1. CRP is the journey from code to pixels
  2. CSS blocks rendering - browser waits for all CSS before displaying anything
  3. JavaScript blocks parsing - browser stops parsing HTML when it encounters <script>
  4. Use async for independent scripts (analytics)
  5. Use defer for scripts that need DOM (your main app)
  6. Inline critical CSS for above-the-fold content
  7. Minimize reflows by batching DOM changes
  8. Use transform and opacity for animations (GPU-accelerated)

Understanding CRP helps you build faster websites that load and respond quickly!

No comments:

Post a Comment

What is slice() in JavaScript

What is slice() ? slice() is a method used to copy a portion of an array or string without changing the original . Think of it like cut...