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:
- No CDN (slow DNS + connect)
- No caching (slow response)
- 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.