Back to blog
PerformanceJanuary 1, 2024· 5 min read

Resource Timing: See Exactly Why Websites Are Slow

A practical Fusebox guide to resource timing.

Resource Timing: See Exactly Why Websites Are Slow

Published: January 2024
Reading time: 7 minutes

Every millisecond counts. Resource Timing API reveals exactly where time goes when loading a website - DNS lookups, SSL handshakes, server response, download time. Here's how to use it.

What Resource Timing Shows

For every resource (images, scripts, stylesheets), you get:

{
  name: "https://example.com/app.js",
  startTime: 100.5,
  duration: 234.2,
  
  // Detailed breakdown:
  redirectTime: 0,
  dnsLookupTime: 12.1,
  tcpHandshakeTime: 45.3,
  secureConnectionTime: 22.4,
  responseTime: 89.2,
  downloadTime: 65.2
}

Each phase tells a story about performance.

The Resource Loading Timeline

1. Redirect (if any)

redirectStart → redirectEnd
Duration: 0-50ms (good), 100ms+ (problem)

What happens: Following 301/302 redirects Common issue: Chain redirects (A→B→C)

2. DNS Lookup

domainLookupStart → domainLookupEnd
Duration: 0-50ms (good), 200ms+ (problem)

What happens: Converting domain to IP Fix: DNS prefetch, better DNS provider

3. TCP Connection

connectStart → connectEnd
Duration: 10-50ms (good), 150ms+ (problem)

What happens: Establishing connection Fix: Keep-alive, HTTP/2, closer servers

4. SSL Negotiation

secureConnectionStart → connectEnd
Duration: 10-50ms (good), 200ms+ (problem)

What happens: HTTPS handshake Fix: TLS 1.3, session resumption

5. Request/Response

requestStart → responseStart
Duration: 50-200ms (good), 500ms+ (problem)

What happens: Server processing Fix: Caching, faster backend, CDN

6. Download

responseStart → responseEnd
Duration: Varies by size

What happens: Transferring data Fix: Compression, smaller files

Real-World Analysis

Example 1: Slow Images

Resource timing shows:

{
  name: "hero-image.jpg",
  duration: 2845ms,
  
  // Breakdown:
  dnsLookup: 180ms,      // Slow DNS
  connect: 250ms,        // Far server
  response: 890ms,       // Not cached
  download: 1525ms       // 3MB image!
}

Problems found:

  1. No CDN (slow DNS + connect)
  2. No caching (slow response)
  3. Unoptimized image (slow download)

Solutions:

<!-- Before -->
<img src="https://myserver.com/hero-image.jpg">

<!-- After -->
<img src="https://cdn.example.com/hero-image-optimized.webp" 
     loading="lazy" width="1200" height="600">

Example 2: JavaScript Bottleneck

Multiple scripts blocking:

// main.js: 890ms total
{
  dns: 0ms,          // Already resolved
  connect: 0ms,      // Connection reused
  response: 623ms,   // Server generation
  download: 267ms    // 385KB file
}

// vendor.js: 1240ms total
{
  response: 856ms,   // Waiting for main.js
  download: 384ms    // 512KB file
}

Fix: Parallel loading

<!-- Before: Sequential -->
<script src="main.js"></script>
<script src="vendor.js"></script>

<!-- After: Parallel -->
<script defer src="main.js"></script>
<script defer src="vendor.js"></script>

Example 3: Third-Party Impact

Analytics and ads timing:

// Your resources: Average 200ms
// Third parties:
{
  "googletagmanager.com": 450ms,
  "facebook.com": 680ms,
  "doubleclick.net": 920ms,
  "hotjar.com": 340ms
}

Total third-party time: 2.39 seconds!

Measuring Resource Timing

1. Performance Observer API

// Monitor all resources
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(`${entry.name}: ${entry.duration}ms`);
    
    // Detailed timing
    const dns = entry.domainLookupEnd - entry.domainLookupStart;
    const tcp = entry.connectEnd - entry.connectStart;
    const ssl = entry.secureConnectionStart > 0 
      ? entry.connectEnd - entry.secureConnectionStart 
      : 0;
    const ttfb = entry.responseStart - entry.requestStart;
    const download = entry.responseEnd - entry.responseStart;
    
    console.log(`  DNS: ${dns}ms, TCP: ${tcp}ms, SSL: ${ssl}ms`);
    console.log(`  TTFB: ${ttfb}ms, Download: ${download}ms`);
  }
});

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

2. Get All Resources

// Snapshot of all loaded resources
const resources = performance.getEntriesByType('resource');

// Sort by duration
resources.sort((a, b) => b.duration - a.duration);

// Top 10 slowest
console.table(resources.slice(0, 10).map(r => ({
  name: r.name.split('/').pop(),
  duration: Math.round(r.duration),
  size: Math.round(r.transferSize / 1024) + 'KB'
})));

3. Resource Type Analysis

// Group by type
const byType = {};

performance.getEntriesByType('resource').forEach(resource => {
  const ext = resource.name.split('.').pop().split('?')[0];
  const type = getResourceType(ext); // js, css, image, font, etc.
  
  if (!byType[type]) {
    byType[type] = { count: 0, duration: 0, size: 0 };
  }
  
  byType[type].count++;
  byType[type].duration += resource.duration;
  byType[type].size += resource.transferSize || 0;
});

console.table(byType);

Performance Patterns

Good Pattern: CDN + Caching

{
  name: "https://cdn.example.com/style.css",
  duration: 45ms,
  
  dns: 0ms,        // Cached DNS
  connect: 0ms,    // Reused connection
  response: 12ms,  // Edge server
  download: 33ms   // Small, compressed
}

Bad Pattern: No Optimization

{
  name: "https://example.com/everything.js",
  duration: 1847ms,
  
  dns: 156ms,      // No DNS prefetch
  connect: 234ms,  // New connection
  ssl: 189ms,      // Full handshake
  response: 567ms, // No cache
  download: 701ms  // 2MB uncompressed
}

Waterfall Analysis

|-- DNS --|-- TCP --|-- SSL --|-- Wait --|-- Download --|
     ↑         ↑         ↑          ↑            ↑
   156ms     234ms     189ms      567ms       701ms
                              
Total: 1847ms (Too slow!)

Optimization Strategies

1. DNS Prefetch

<!-- Tell browser to resolve DNS early -->
<link rel="dns-prefetch" href="//cdn.example.com">
<link rel="dns-prefetch" href="//fonts.googleapis.com">

Impact: Saves 50-300ms per domain

2. Preconnect

<!-- Establish connection early -->
<link rel="preconnect" href="https://api.example.com">
<link rel="preconnect" href="https://cdn.example.com" crossorigin>

Impact: Saves DNS + TCP + SSL time

3. Resource Hints Priority

<!-- Critical resources -->
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="font.woff2" as="font" crossorigin>

<!-- Next navigation -->
<link rel="prefetch" href="/next-page.html">
<link rel="prefetch" href="/next-page.css">

4. HTTP/2 Push (Use Carefully)

Link: </app.css>; rel=preload; as=style

Warning: Often hurts performance if overused

Resource Timing Budgets

Set Performance Budgets

const budgets = {
  css: { maxDuration: 200, maxSize: 50 * 1024 },
  js: { maxDuration: 500, maxSize: 200 * 1024 },
  image: { maxDuration: 1000, maxSize: 200 * 1024 },
  font: { maxDuration: 300, maxSize: 100 * 1024 }
};

// Monitor violations
performance.getEntriesByType('resource').forEach(resource => {
  const type = getResourceType(resource.name);
  const budget = budgets[type];
  
  if (budget) {
    if (resource.duration > budget.maxDuration) {
      console.warn(`Slow ${type}: ${resource.name} took ${resource.duration}ms`);
    }
    if (resource.transferSize > budget.maxSize) {
      console.warn(`Large ${type}: ${resource.name} is ${resource.transferSize} bytes`);
    }
  }
});

Quick Wins Checklist

Reduce DNS Time

  • Use DNS prefetch for external domains
  • Minimize unique domains
  • Use fast DNS providers

Reduce Connection Time

  • Use preconnect for critical origins
  • Enable HTTP/2
  • Use persistent connections

Reduce Response Time

  • Add caching headers
  • Use CDN for static assets
  • Optimize server response time

Reduce Download Time

  • Enable compression (gzip/brotli)
  • Optimize images (WebP, AVIF)
  • Minify CSS/JS
  • Use resource bundling wisely

Debugging Slow Resources

Find the Culprits

// Get slowest resources
const slow = performance.getEntriesByType('resource')
  .filter(r => r.duration > 500)
  .sort((a, b) => b.duration - a.duration);

slow.forEach(resource => {
  console.log(`Slow resource: ${resource.name}`);
  console.log(`  Total: ${resource.duration}ms`);
  console.log(`  DNS: ${resource.domainLookupEnd - resource.domainLookupStart}ms`);
  console.log(`  Connect: ${resource.connectEnd - resource.connectStart}ms`);
  console.log(`  TTFB: ${resource.responseStart - resource.requestStart}ms`);
  console.log(`  Download: ${resource.responseEnd - resource.responseStart}ms`);
});

The Bottom Line

Resource Timing reveals the truth about performance:

  • Every millisecond has a cause
  • Most delays are fixable
  • Small optimizations add up
  • Measure everything to improve

Stop guessing why sites are slow. Resource Timing shows you exactly where time goes.


Monitor resource timing live: Fusebox tracks detailed timing for every resource as you browse. See DNS, SSL, download times instantly. $29 one-time purchase.