← Back to Performance

Optimizing the critical rendering path for instant loading

PROCESSBrowser Rendering Process

Understanding how browsers process and render web pages

Browser Rendering Pipeline

1
Parse HTML → DOM

HTML parser creates Document Object Model

~10ms
2
Parse CSS → CSSOM

CSS parser creates CSS Object Model

~5ms
3
DOM + CSSOM → Render Tree

Combine visible elements with styles

~2ms
4
Layout (Reflow)

Calculate element positions and sizes

~15ms
5
Paint & Composite

Render pixels and composite layers

~8ms

Critical Resources

HTML Document

Blocks DOM construction

Cannot render without DOM

Critical CSS

Blocks first render

Above-the-fold styles

Blocking JS

Stops HTML parsing

Script without async/defer

CSSCritical CSS Optimization

Extracting and inlining critical CSS for faster first render

📤 CSS Extraction

// Critical CSS tools
npm install --save-dev critical

// Usage in build process
const critical = require('critical')

critical.generate({
  base: 'dist/',
  src: 'index.html',
  dest: 'index-critical.html',
  width: 1300,
  height: 900,
  minify: true,
  extract: true,
  inlineImages: false,
  dimensions: [
    { width: 320, height: 480 },  // Mobile
    { width: 768, height: 1024 }, // Tablet
    { width: 1300, height: 900 }  // Desktop
  ]
})

// Webpack plugin
const HtmlWebpackPlugin = require('html-webpack-plugin')
const HtmlCriticalWebpackPlugin = require('html-critical-webpack-plugin')

module.exports = {
  plugins: [
    new HtmlWebpackPlugin(),
    new HtmlCriticalWebpackPlugin({
      base: path.resolve(__dirname, 'dist'),
      src: 'index.html',
      dest: 'index.html',
      inline: true,
      minify: true,
      extract: true,
      width: 375,
      height: 565,
      penthouse: { blockJSRequests: false }
    })
  ]
}

⚡ Loading Strategy

<!-- Inline critical CSS -->
<style>
/* Critical above-the-fold CSS */
.header { display: block; }
.hero { height: 100vh; }
.fold { margin-bottom: 2rem; }
</style>

<!-- Preload non-critical CSS -->
<link rel="preload" href="/styles/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/styles/main.css"></noscript>

<!-- Async CSS loading function -->
<script>
function loadCSS(href) {
  const link = document.createElement('link')
  link.rel = 'stylesheet'
  link.href = href
  link.media = 'print'
  link.onload = () => { link.media = 'all' }
  document.head.appendChild(link)
}

// Load non-critical CSS
loadCSS('/styles/components.css')
loadCSS('/styles/pages.css')
</script>

<!-- Resource hints -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="dns-prefetch" href="//cdn.example.com">
JSJavaScript Loading Optimization

Optimizing JavaScript to avoid blocking the critical rendering path

<!-- Different JavaScript loading strategies -->

<!-- ❌ Blocking script (blocks HTML parsing) -->
<script src="blocking-script.js"></script>

<!-- ✅ Async script (downloads in parallel, executes when ready) -->
<script async src="analytics.js"></script>

<!-- ✅ Defer script (downloads in parallel, executes after DOM ready) -->
<script defer src="app.js"></script>

<!-- ✅ Module script (modern browsers, automatic defer) -->
<script type="module" src="modern.js"></script>
<script nomodule src="legacy.js"></script>

<!-- ✅ Dynamic import (load on demand) -->
<script>
// Load feature when needed
document.getElementById('feature-button').addEventListener('click', async () => {
  const { initFeature } = await import('./feature.js')
  initFeature()
})

// Preload critical JavaScript
const link = document.createElement('link')
link.rel = 'preload'
link.as = 'script'
link.href = '/critical.js'
document.head.appendChild(link)
</script>

<!-- ✅ Service Worker for caching -->
<script>
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
}
</script>
PRIORITYResource Prioritization

Using browser hints to optimize resource loading priority

<!-- Resource hints for better loading -->

<!-- Preconnect to external domains -->
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
<link rel="preconnect" href="https://api.example.com" crossorigin>

<!-- DNS prefetch for late-discovered resources -->
<link rel="dns-prefetch" href="//cdn.example.com">
<link rel="dns-prefetch" href="//analytics.google.com">

<!-- Preload critical resources -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/hero-image.webp" as="image">
<link rel="preload" href="/critical.css" as="style">
<link rel="preload" href="/app.js" as="script">

<!-- Prefetch likely next resources -->
<link rel="prefetch" href="/page2.html">
<link rel="prefetch" href="/components.js">

<!-- Module preload for ES modules -->
<link rel="modulepreload" href="/modules/app.js">

<!-- Fetch priority (experimental) -->
<script fetchpriority="high" src="/critical.js"></script>
<img src="/hero.jpg" fetchpriority="high" alt="Hero">
<img src="/secondary.jpg" fetchpriority="low" alt="Secondary">

<!-- Resource hints in JavaScript -->
const link = document.createElement('link')
link.rel = 'preload'
link.href = '/dynamic-resource.js'
link.as = 'script'
document.head.appendChild(link)
PATTERNSCritical Path Patterns

Common patterns for optimizing the critical rendering path

🚀 PRPL Pattern

Push

Critical resources for initial route

Render

Initial route as soon as possible

Pre-cache

Remaining routes using Service Worker

Lazy-load

Create remaining routes on demand

🏠 App Shell Model

Header (Shell)
Dynamic Content
Loaded separately
Footer (Shell)

Shell cached, content dynamic

MEASUREMeasuring Critical Path

Tools and metrics for measuring critical rendering path performance

// Critical metrics measurement
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    switch (entry.entryType) {
      case 'paint':
        if (entry.name === 'first-contentful-paint') {
          console.log('FCP:', entry.startTime)
        }
        break

      case 'largest-contentful-paint':
        console.log('LCP:', entry.startTime)
        break

      case 'layout-shift':
        console.log('CLS:', entry.value)
        break
    }
  }
})

observer.observe({
  entryTypes: ['paint', 'largest-contentful-paint', 'layout-shift']
})

// Critical resource timing
window.addEventListener('load', () => {
  const resources = performance.getEntriesByType('resource')

  // Identify render-blocking resources
  const blockingResources = resources.filter(resource => {
    return resource.renderBlockingStatus === 'blocking'
  })

  console.log('Blocking resources:', blockingResources.length)

  // Measure critical path metrics
  const navigation = performance.getEntriesByType('navigation')[0]
  const domComplete = navigation.domComplete
  const loadComplete = navigation.loadEventEnd

  console.log('Critical path time:', domComplete)
  console.log('Full load time:', loadComplete)
})

// Lighthouse programmatic audit
const lighthouse = require('lighthouse')
const chromeLauncher = require('chrome-launcher')

async function auditCriticalPath(url) {
  const chrome = await chromeLauncher.launch()
  const options = { port: chrome.port }

  const result = await lighthouse(url, options)
  const audits = result.lhr.audits

  return {
    fcp: audits['first-contentful-paint'].numericValue,
    lcp: audits['largest-contentful-paint'].numericValue,
    renderBlocking: audits['render-blocking-resources'].score,
    unusedCSS: audits['unused-css-rules'].score,
    criticalRequest: audits['critical-request-chains'].displayValue
  }
}
TIPSCritical Path Optimization

Complete checklist for critical rendering path optimization

✅ OPTIMIZATIONS

  • • Minimize critical resource count
  • • Reduce critical resource sizes
  • • Inline critical CSS
  • • Defer non-critical JavaScript
  • • Preload critical resources
  • • Compress and minify assets
  • • Use efficient image formats
  • • Enable HTTP/2 server push
  • • Implement service worker caching
  • • Optimize web font loading

❌ AVOID

  • • Large blocking JavaScript files
  • • Render-blocking CSS imports
  • • Multiple CSS files in critical path
  • • Uncompressed critical resources
  • • Missing resource preloads
  • • Blocking third-party scripts
  • • Large above-the-fold images
  • • Synchronous XHR requests
  • • Complex CSS selectors
  • • Forced synchronous layouts