Front-end Engineering Lab

Network Waterfall Analysis

Analyze and optimize network request timing

Network waterfalls visualize request timing—crucial for optimizing load performance and identifying bottlenecks.

Reading the Waterfall

Request Timeline:
├── Queueing (gray) - Waiting for connection slot
├── Stalled (gray) - Waiting for available connection
├── DNS Lookup (teal) - Resolving domain
├── Initial Connection (orange) - TCP handshake
├── SSL (purple) - TLS negotiation
├── Request Sent (green) - Uploading request
├── Waiting (green) - TTFB (Time to First Byte)
└── Content Download (blue) - Downloading response

Capturing Network Timing

// Resource Timing API
export function getResourceTiming(url: string) {
  const entries = performance.getEntriesByName(url, 'resource');
  
  if (entries.length === 0) return null;
  
  const entry = entries[0] as PerformanceResourceTiming;
  
  return {
    dns: entry.domainLookupEnd - entry.domainLookupStart,
    tcp: entry.connectEnd - entry.connectStart,
    ssl: entry.secureConnectionStart > 0
      ? entry.connectEnd - entry.secureConnectionStart
      : 0,
    ttfb: entry.responseStart - entry.requestStart,
    download: entry.responseEnd - entry.responseStart,
    total: entry.responseEnd - entry.startTime,
  };
}

// Monitor all requests
const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    if (entry.entryType === 'resource') {
      const timing = entry as PerformanceResourceTiming;
      
      // Log slow requests
      if (timing.duration > 1000) {
        console.warn('Slow request:', timing.name, `${timing.duration}ms`);
      }
    }
  });
});

observer.observe({ entryTypes: ['resource'] });

Analyzing Patterns

Request Waterfall Analyzer

export function analyzeNetworkWaterfall() {
  const resources = performance.getEntriesByType('resource');
  
  const analysis = {
    totalRequests: resources.length,
    totalSize: 0,
    totalTime: 0,
    byType: {} as Record<string, number>,
    slowRequests: [] as any[],
    parallelism: 0,
  };

  resources.forEach((resource: any) => {
    // Count by type
    const type = resource.initiatorType;
    analysis.byType[type] = (analysis.byType[type] || 0) + 1;
    
    // Track size
    analysis.totalSize += resource.transferSize || 0;
    
    // Track slow requests
    if (resource.duration > 1000) {
      analysis.slowRequests.push({
        name: resource.name,
        duration: resource.duration,
        size: resource.transferSize,
      });
    }
  });

  // Calculate parallelism
  const timeline = resources.map((r: any) => ({
    start: r.startTime,
    end: r.startTime + r.duration,
  }));
  
  analysis.parallelism = calculateMaxParallel(timeline);

  return analysis;
}

function calculateMaxParallel(timeline: Array<{ start: number; end: number }>) {
  const events: Array<{ time: number; type: 'start' | 'end' }> = [];
  
  timeline.forEach(({ start, end }) => {
    events.push({ time: start, type: 'start' });
    events.push({ time: end, type: 'end' });
  });
  
  events.sort((a, b) => a.time - b.time);
  
  let current = 0;
  let max = 0;
  
  events.forEach(event => {
    if (event.type === 'start') {
      current++;
      max = Math.max(max, current);
    } else {
      current--;
    }
  });
  
  return max;
}

Optimization Strategies

HTTP/2 Multiplexing

// Check if HTTP/2 is used
const entry = performance.getEntriesByType('navigation')[0] as any;
console.log('Protocol:', entry.nextHopProtocol); // Should be 'h2'

// With HTTP/2:
// - Multiple requests over single connection
// - No 6 connection limit
// - Header compression

Resource Hints

// Preconnect to third-party origins
export function ResourceHints() {
  return (
    <>
      <link rel="preconnect" href="https://api.example.com" />
      <link rel="dns-prefetch" href="https://cdn.example.com" />
      <link rel="preload" href="/critical.js" as="script" />
    </>
  );
}

Request Prioritization

// Priority hints (Chrome)
<link rel="preload" href="/important.css" as="style" fetchpriority="high" />
<img src="/hero.jpg" fetchpriority="high" />
<script src="/analytics.js" fetchpriority="low" />

Monitoring Network Performance

export function useNetworkMonitor() {
  useEffect(() => {
    const observer = new PerformanceObserver((list) => {
      list.getEntries().forEach((entry) => {
        const resource = entry as PerformanceResourceTiming;
        
        // Log metrics
        logMetric({
          type: 'network_request',
          url: resource.name,
          duration: resource.duration,
          size: resource.transferSize,
          cached: resource.transferSize === 0,
          protocol: resource.nextHopProtocol,
        });
      });
    });

    observer.observe({ entryTypes: ['resource'] });

    return () => observer.disconnect();
  }, []);
}

Best Practices

  1. Minimize requests - Bundle, sprite sheets
  2. Enable HTTP/2 - Multiplexing, compression
  3. Use CDN - Reduce latency
  4. Compress assets - Gzip/Brotli
  5. Cache aggressively - Long cache headers
  6. Preconnect to origins - Save DNS/TCP time
  7. Prioritize critical resources - fetchpriority hint
  8. Monitor in production - Track P95 latency

Network waterfalls reveal bottlenecks—optimize critical path for fastest load times!

On this page