PatternsObservability
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 responseCapturing 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 compressionResource 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
- Minimize requests - Bundle, sprite sheets
- Enable HTTP/2 - Multiplexing, compression
- Use CDN - Reduce latency
- Compress assets - Gzip/Brotli
- Cache aggressively - Long cache headers
- Preconnect to origins - Save DNS/TCP time
- Prioritize critical resources - fetchpriority hint
- Monitor in production - Track P95 latency
Network waterfalls reveal bottlenecks—optimize critical path for fastest load times!