Back to blog
InspectionJanuary 1, 2024Β· 16 min read

Website Caching Layers: Analyze and Optimize Every Cache Level

A practical Fusebox guide to website caching layers.

Website Caching Layers: Analyze and Optimize Every Cache Level

Published: January 2024
Reading time: 10 minutes

Caching makes websites fast, but misconfigured caches cause stale content, security leaks, and debugging nightmares. Modern sites use multiple cache layersβ€”browser, CDN, server, application. Here's how to analyze each layer, detect issues, and optimize for performance.

Why Caching Analysis Matters

The Performance Impact

  • First load: 3-10x faster with proper caching
  • Repeat visits: 50-100x faster from browser cache
  • Server load: 90% reduction with edge caching
  • Bandwidth costs: 80% lower with CDN caching
  • User experience: Instant page loads

Real Caching Failures

GitHub (2020): Cached sensitive API responses leaked data
Amazon (2019): Stale cache showed wrong prices for hours
News Sites: Outdated articles during breaking news
E-commerce: Cart contents cached between users
Banking: Account balances cached incorrectly

Quick Cache Detection

1. Browser Cache Analyzer

// Analyze browser caching for all resources
(function analyzeBrowserCache() {
  console.log('πŸ—„οΈ Browser Cache Analysis\n');
  
  const resources = performance.getEntriesByType('resource');
  const cacheStats = {
    total: resources.length,
    cached: 0,
    uncached: 0,
    partial: 0,
    byType: {},
    issues: []
  };
  
  // Analyze each resource
  resources.forEach(resource => {
    const wasCached = resource.transferSize === 0 && resource.decodedBodySize > 0;
    const wasPartial = resource.transferSize > 0 && resource.transferSize < resource.decodedBodySize;
    
    if (wasCached) {
      cacheStats.cached++;
    } else if (wasPartial) {
      cacheStats.partial++;
    } else {
      cacheStats.uncached++;
    }
    
    // Group by type
    const type = getResourceType(resource.name);
    if (!cacheStats.byType[type]) {
      cacheStats.byType[type] = { total: 0, cached: 0 };
    }
    cacheStats.byType[type].total++;
    if (wasCached) cacheStats.byType[type].cached++;
    
    // Check for cache issues
    if (type === 'html' && wasCached && resource.duration > 1000) {
      cacheStats.issues.push({
        resource: resource.name,
        issue: 'HTML cached but slow',
        impact: 'Possible stale content'
      });
    }
    
    if (type === 'api' && wasCached) {
      cacheStats.issues.push({
        resource: resource.name,
        issue: 'API response cached',
        impact: 'Possible stale data'
      });
    }
  });
  
  // Calculate cache hit rate
  const cacheHitRate = ((cacheStats.cached / cacheStats.total) * 100).toFixed(1);
  
  // Display results
  console.log(`πŸ“Š Overall Cache Performance:`);
  console.log(`Total resources: ${cacheStats.total}`);
  console.log(`Cache hit rate: ${cacheHitRate}%`);
  console.log(`  βœ… Cached: ${cacheStats.cached}`);
  console.log(`  ❌ Not cached: ${cacheStats.uncached}`);
  console.log(`  πŸ”„ Partial: ${cacheStats.partial}\n`);
  
  console.log('πŸ“ˆ Cache Hit Rate by Type:');
  Object.entries(cacheStats.byType).forEach(([type, stats]) => {
    const hitRate = ((stats.cached / stats.total) * 100).toFixed(0);
    const icon = hitRate > 80 ? 'βœ…' : hitRate > 50 ? '🟑' : '❌';
    console.log(`${icon} ${type}: ${hitRate}% (${stats.cached}/${stats.total})`);
  });
  
  // Check cache headers
  console.log('\nπŸ” Checking Cache Headers...');
  
  // Make test request to check headers
  fetch(window.location.href, { method: 'HEAD' })
    .then(response => {
      console.log('\nCache Control Headers:');
      
      const cacheHeaders = {
        'cache-control': response.headers.get('cache-control'),
        'expires': response.headers.get('expires'),
        'etag': response.headers.get('etag'),
        'last-modified': response.headers.get('last-modified'),
        'vary': response.headers.get('vary'),
        'age': response.headers.get('age')
      };
      
      Object.entries(cacheHeaders).forEach(([header, value]) => {
        if (value) {
          console.log(`  ${header}: ${value}`);
          
          // Analyze cache-control
          if (header === 'cache-control') {
            analyzeCacheControl(value);
          }
        }
      });
      
      // CDN detection
      const cdnHeaders = {
        'cf-cache-status': response.headers.get('cf-cache-status'),
        'x-cache': response.headers.get('x-cache'),
        'x-edge-location': response.headers.get('x-edge-location'),
        'x-served-by': response.headers.get('x-served-by')
      };
      
      const detectedCDN = Object.entries(cdnHeaders).find(([_, value]) => value);
      if (detectedCDN) {
        console.log(`\n🌐 CDN Detected: ${detectedCDN[0]} = ${detectedCDN[1]}`);
      }
    })
    .catch(() => console.log('Could not fetch headers'));
  
  // Report issues
  if (cacheStats.issues.length > 0) {
    console.log('\n⚠️ Cache Issues Detected:');
    cacheStats.issues.forEach(issue => {
      console.log(`  ${issue.resource.split('/').pop()}`);
      console.log(`    Issue: ${issue.issue}`);
      console.log(`    Impact: ${issue.impact}`);
    });
  }
  
  // Recommendations
  console.log('\nπŸ’‘ Cache Optimization Tips:');
  if (cacheHitRate < 50) {
    console.log('  1. Add cache headers to static assets');
    console.log('  2. Use versioned filenames for cache busting');
  }
  if (cacheStats.byType.image?.cached < cacheStats.byType.image?.total * 0.8) {
    console.log('  3. Increase image cache duration');
  }
  if (cacheStats.byType.api?.cached > 0) {
    console.log('  4. Review API caching strategy');
  }
  console.log('  5. Implement service worker for offline caching');
  
  return cacheStats;
})();

function getResourceType(url) {
  if (url.match(/\.(jpg|jpeg|png|gif|webp|svg|ico)$/i)) return 'image';
  if (url.match(/\.(js)$/i)) return 'script';
  if (url.match(/\.(css)$/i)) return 'style';
  if (url.match(/\.(woff|woff2|ttf|otf)$/i)) return 'font';
  if (url.includes('/api/') || url.includes('.json')) return 'api';
  if (url.match(/\.(html|htm)$/i) || url.endsWith('/')) return 'html';
  return 'other';
}

function analyzeCacheControl(value) {
  const directives = value.split(',').map(d => d.trim());
  
  console.log('\n  Cache-Control Analysis:');
  
  directives.forEach(directive => {
    if (directive === 'no-cache') {
      console.log('    ⚠️ no-cache: Must revalidate with server');
    } else if (directive === 'no-store') {
      console.log('    ❌ no-store: Prevents all caching');
    } else if (directive.startsWith('max-age=')) {
      const seconds = parseInt(directive.split('=')[1]);
      const duration = seconds > 86400 ? `${(seconds/86400).toFixed(1)} days` :
                      seconds > 3600 ? `${(seconds/3600).toFixed(1)} hours` :
                      `${seconds} seconds`;
      console.log(`    ⏱️ max-age: ${duration}`);
    } else if (directive === 'public') {
      console.log('    βœ… public: Can be cached by CDNs');
    } else if (directive === 'private') {
      console.log('    πŸ”’ private: Browser cache only');
    } else if (directive === 'immutable') {
      console.log('    πŸ’Ž immutable: Content never changes');
    } else if (directive.includes('stale-while-revalidate')) {
      console.log('    πŸ”„ stale-while-revalidate: Serves stale while updating');
    }
  });
}

2. Service Worker Cache Inspector

// Inspect Service Worker caching
async function inspectServiceWorker() {
  console.log('\nπŸ‘· Service Worker Cache Inspector\n');
  
  if (!('serviceWorker' in navigator)) {
    console.log('❌ Service Workers not supported');
    return;
  }
  
  // Check registration
  const registration = await navigator.serviceWorker.getRegistration();
  if (!registration) {
    console.log('❌ No Service Worker registered');
    return;
  }
  
  console.log('βœ… Service Worker detected');
  console.log(`  Scope: ${registration.scope}`);
  console.log(`  State: ${registration.active?.state || 'not active'}`);
  
  // Check cache storage
  if ('caches' in window) {
    console.log('\nπŸ“¦ Cache Storage Analysis:');
    
    try {
      const cacheNames = await caches.keys();
      console.log(`  Cache instances: ${cacheNames.length}`);
      
      let totalSize = 0;
      let totalEntries = 0;
      
      for (const cacheName of cacheNames) {
        console.log(`\n  πŸ“ ${cacheName}:`);
        
        const cache = await caches.open(cacheName);
        const requests = await cache.keys();
        
        console.log(`    Entries: ${requests.length}`);
        
        // Sample entries
        const samples = requests.slice(0, 5);
        samples.forEach(request => {
          const url = new URL(request.url);
          console.log(`    - ${url.pathname}`);
        });
        
        if (requests.length > 5) {
          console.log(`    ... and ${requests.length - 5} more`);
        }
        
        totalEntries += requests.length;
        
        // Estimate size (would need to fetch each to get real size)
        totalSize += requests.length * 50000; // 50KB average estimate
      }
      
      console.log(`\n  Total cached entries: ${totalEntries}`);
      console.log(`  Estimated size: ${(totalSize / 1024 / 1024).toFixed(1)}MB`);
      
      // Check for common patterns
      console.log('\nπŸ” Cache Strategy Detection:');
      
      if (cacheNames.some(name => name.includes('runtime'))) {
        console.log('  βœ… Runtime caching detected');
      }
      if (cacheNames.some(name => name.includes('precache'))) {
        console.log('  βœ… Precaching detected');
      }
      if (cacheNames.some(name => name.includes('images'))) {
        console.log('  βœ… Separate image cache detected');
      }
      if (cacheNames.some(name => name.includes('api'))) {
        console.log('  βœ… API response caching detected');
      }
      
    } catch (error) {
      console.log('❌ Could not access cache storage:', error.message);
    }
  }
  
  // Check for update
  console.log('\nπŸ”„ Checking for updates...');
  registration.addEventListener('updatefound', () => {
    console.log('  πŸ†• New Service Worker available!');
  });
  
  try {
    await registration.update();
    console.log('  βœ… Update check complete');
  } catch (error) {
    console.log('  ❌ Update check failed:', error.message);
  }
  
  // Best practices check
  console.log('\nβœ… Service Worker Best Practices:');
  
  const practices = {
    'Versioned caches': cacheNames.some(name => /v\d+/.test(name)),
    'Separate caches by type': cacheNames.length > 1,
    'Scope is root': registration.scope === '/',
    'Active worker': registration.active !== null
  };
  
  Object.entries(practices).forEach(([practice, implemented]) => {
    console.log(`  ${implemented ? 'βœ…' : '❌'} ${practice}`);
  });
}

inspectServiceWorker();

3. CDN Cache Analyzer

// Analyze CDN caching behavior
async function analyzeCDNCache() {
  console.log('\n🌐 CDN Cache Analysis\n');
  
  const cdnTests = {
    cloudflare: {
      headers: ['cf-cache-status', 'cf-ray'],
      statuses: {
        'HIT': 'βœ… Served from Cloudflare cache',
        'MISS': '❌ Not in cache, fetched from origin',
        'BYPASS': 'πŸ”„ Cache bypassed',
        'EXPIRED': '⏰ Cache expired, revalidating',
        'STALE': 'πŸ“¦ Serving stale content',
        'REVALIDATED': 'βœ”οΈ Cache revalidated',
        'DYNAMIC': 'πŸ”€ Dynamic content, not cached'
      }
    },
    cloudfront: {
      headers: ['x-cache'],
      statuses: {
        'Hit from cloudfront': 'βœ… CloudFront cache hit',
        'Miss from cloudfront': '❌ CloudFront cache miss',
        'RefreshHit from cloudfront': 'πŸ”„ Revalidated from origin'
      }
    },
    fastly: {
      headers: ['x-cache', 'x-cache-hits'],
      statuses: {
        'HIT': 'βœ… Fastly cache hit',
        'MISS': '❌ Fastly cache miss'
      }
    },
    akamai: {
      headers: ['x-cache', 'x-cache-key'],
      statuses: {
        'TCP_HIT': 'βœ… Akamai cache hit',
        'TCP_MISS': '❌ Akamai cache miss'
      }
    }
  };
  
  // Test current page
  try {
    const response = await fetch(window.location.href, { 
      method: 'HEAD',
      cache: 'no-cache' // Force fresh request
    });
    
    let cdnDetected = null;
    let cacheStatus = null;
    
    // Check each CDN
    for (const [cdn, config] of Object.entries(cdnTests)) {
      for (const header of config.headers) {
        const value = response.headers.get(header);
        if (value) {
          cdnDetected = cdn;
          console.log(`🌐 CDN Detected: ${cdn.toUpperCase()}`);
          console.log(`  ${header}: ${value}`);
          
          // Check cache status
          for (const [status, description] of Object.entries(config.statuses)) {
            if (value.toLowerCase().includes(status.toLowerCase())) {
              cacheStatus = description;
              console.log(`  Status: ${description}`);
              break;
            }
          }
          break;
        }
      }
      if (cdnDetected) break;
    }
    
    if (!cdnDetected) {
      console.log('❌ No CDN detected or cache headers hidden');
    }
    
    // Additional CDN headers
    const additionalHeaders = {
      'age': 'Resource age in cache',
      'x-edge-location': 'Edge server location',
      'x-served-by': 'Server identifier',
      'vary': 'Cache key variations',
      'x-cache-hits': 'Number of cache hits'
    };
    
    console.log('\nπŸ“‹ Additional Cache Headers:');
    Object.entries(additionalHeaders).forEach(([header, description]) => {
      const value = response.headers.get(header);
      if (value) {
        console.log(`  ${header}: ${value} (${description})`);
      }
    });
    
    // Test multiple resources
    console.log('\nπŸ” Testing Resource Caching:');
    
    const resources = performance.getEntriesByType('resource')
      .filter(r => r.name.includes(window.location.hostname))
      .slice(0, 5);
    
    for (const resource of resources) {
      const type = getResourceType(resource.name);
      const response = await fetch(resource.name, { method: 'HEAD' });
      
      let status = 'Unknown';
      if (cdnDetected) {
        const header = cdnTests[cdnDetected].headers[0];
        const value = response.headers.get(header);
        if (value) {
          status = value;
        }
      }
      
      console.log(`  ${type}: ${status}`);
    }
    
  } catch (error) {
    console.log('❌ Could not analyze CDN cache:', error.message);
  }
  
  // CDN optimization tips
  console.log('\nπŸ’‘ CDN Optimization Tips:');
  console.log('  1. Use cache headers to control CDN behavior');
  console.log('  2. Implement cache purging for updates');
  console.log('  3. Use different cache times for different content');
  console.log('  4. Monitor cache hit rates');
  console.log('  5. Use cache keys wisely (Vary header)');
}

analyzeCDNCache();

Cache Strategy Analysis

1. Cache Header Optimizer

// Analyze and suggest optimal cache headers
function optimizeCacheHeaders() {
  console.log('\nβš™οΈ Cache Header Optimization Analysis\n');
  
  const resources = performance.getEntriesByType('resource');
  const recommendations = new Map();
  
  // Recommended cache durations by type
  const optimalCache = {
    'image': {
      duration: 365 * 24 * 60 * 60, // 1 year
      directive: 'public, max-age=31536000, immutable',
      reason: 'Images rarely change, use versioning for updates'
    },
    'font': {
      duration: 365 * 24 * 60 * 60, // 1 year
      directive: 'public, max-age=31536000, immutable',
      reason: 'Fonts are static, version in filename'
    },
    'script': {
      duration: 30 * 24 * 60 * 60, // 30 days
      directive: 'public, max-age=2592000, stale-while-revalidate=86400',
      reason: 'Scripts change occasionally, use SWR for updates'
    },
    'style': {
      duration: 30 * 24 * 60 * 60, // 30 days
      directive: 'public, max-age=2592000, stale-while-revalidate=86400',
      reason: 'Styles change occasionally, use SWR for updates'
    },
    'html': {
      duration: 0,
      directive: 'no-cache, no-store, must-revalidate',
      reason: 'HTML should always be fresh'
    },
    'api': {
      duration: 0,
      directive: 'private, no-cache',
      reason: 'API responses need careful caching'
    }
  };
  
  // Analyze each resource
  resources.forEach(resource => {
    const type = getResourceType(resource.name);
    const optimal = optimalCache[type] || optimalCache['script'];
    
    // Check if resource has version/hash in filename
    const hasVersion = /[.-]([0-9a-f]{8,}|v?\d+\.\d+\.\d+)[.-]/i.test(resource.name);
    
    if (!recommendations.has(type)) {
      recommendations.set(type, []);
    }
    
    recommendations.get(type).push({
      url: resource.name,
      hasVersion: hasVersion,
      optimal: optimal
    });
  });
  
  // Generate recommendations
  console.log('πŸ“‹ Cache Header Recommendations by Type:\n');
  
  recommendations.forEach((resources, type) => {
    console.log(`πŸ“ ${type.toUpperCase()} Resources:`);
    console.log(`  Recommended: ${optimalCache[type]?.directive || 'varies'}`);
    console.log(`  Reason: ${optimalCache[type]?.reason || 'depends on use case'}`);
    
    // Check versioning
    const versionedCount = resources.filter(r => r.hasVersion).length;
    const unversionedCount = resources.length - versionedCount;
    
    if (unversionedCount > 0 && ['script', 'style', 'image'].includes(type)) {
      console.log(`  ⚠️ ${unversionedCount} files without version in filename`);
      console.log(`     Consider adding hash or version for cache busting`);
    }
    
    console.log('');
  });
  
  // Generate example headers
  console.log('πŸ“ Example Implementation:\n');
  
  console.log('Nginx configuration:');
  console.log('```nginx');
  console.log('# Images, fonts - 1 year');
  console.log('location ~* \\.(jpg|jpeg|png|gif|ico|css|js|woff|woff2)$ {');
  console.log('    add_header Cache-Control "public, max-age=31536000, immutable";');
  console.log('}');
  console.log('\n# HTML - no cache');
  console.log('location ~* \\.html$ {');
  console.log('    add_header Cache-Control "no-cache, no-store, must-revalidate";');
  console.log('}');
  console.log('```');
  
  console.log('\nApache .htaccess:');
  console.log('```apache');
  console.log('<IfModule mod_expires.c>');
  console.log('    # Images');
  console.log('    ExpiresByType image/jpeg "access plus 1 year"');
  console.log('    ExpiresByType image/png "access plus 1 year"');
  console.log('    \n    # CSS/JS');
  console.log('    ExpiresByType text/css "access plus 1 month"');
  console.log('    ExpiresByType application/javascript "access plus 1 month"');
  console.log('</IfModule>');
  console.log('```');
  
  return recommendations;
}

optimizeCacheHeaders();

2. Cache Performance Monitor

// Monitor cache performance in real-time
class CachePerformanceMonitor {
  constructor() {
    this.metrics = {
      requests: 0,
      cacheHits: 0,
      cacheMisses: 0,
      bytesServedFromCache: 0,
      bytesFromNetwork: 0,
      avgCacheTime: 0,
      avgNetworkTime: 0
    };
    
    this.startMonitoring();
  }
  
  startMonitoring() {
    console.log('\nπŸ“Š Real-time Cache Performance Monitor\n');
    
    // Override fetch to monitor
    const originalFetch = window.fetch;
    window.fetch = async (...args) => {
      const startTime = performance.now();
      const response = await originalFetch(...args);
      const endTime = performance.now();
      const duration = endTime - startTime;
      
      this.recordRequest(args[0], response, duration);
      
      return response;
    };
    
    // Monitor performance entries
    const observer = new PerformanceObserver((list) => {
      list.getEntries().forEach(entry => {
        if (entry.entryType === 'resource') {
          this.analyzeResource(entry);
        }
      });
    });
    
    observer.observe({ entryTypes: ['resource'] });
    
    // Report periodically
    this.reportInterval = setInterval(() => this.report(), 10000);
  }
  
  analyzeResource(entry) {
    this.metrics.requests++;
    
    // Check if served from cache
    const fromCache = entry.transferSize === 0 && entry.decodedBodySize > 0;
    
    if (fromCache) {
      this.metrics.cacheHits++;
      this.metrics.bytesServedFromCache += entry.decodedBodySize;
      this.metrics.avgCacheTime = 
        (this.metrics.avgCacheTime * (this.metrics.cacheHits - 1) + entry.duration) / 
        this.metrics.cacheHits;
    } else {
      this.metrics.cacheMisses++;
      this.metrics.bytesFromNetwork += entry.transferSize || entry.decodedBodySize;
      this.metrics.avgNetworkTime = 
        (this.metrics.avgNetworkTime * (this.metrics.cacheMisses - 1) + entry.duration) / 
        this.metrics.cacheMisses;
    }
  }
  
  recordRequest(url, response, duration) {
    // Additional fetch monitoring
    const cacheStatus = response.headers.get('cf-cache-status') || 
                       response.headers.get('x-cache') || 
                       'unknown';
    
    if (cacheStatus.toLowerCase().includes('hit')) {
      console.log(`βœ… Cache HIT: ${new URL(url).pathname} (${duration.toFixed(0)}ms)`);
    } else if (cacheStatus.toLowerCase().includes('miss')) {
      console.log(`❌ Cache MISS: ${new URL(url).pathname} (${duration.toFixed(0)}ms)`);
    }
  }
  
  report() {
    const hitRate = this.metrics.requests > 0 ? 
      (this.metrics.cacheHits / this.metrics.requests * 100).toFixed(1) : 0;
    
    const mbFromCache = (this.metrics.bytesServedFromCache / 1024 / 1024).toFixed(2);
    const mbFromNetwork = (this.metrics.bytesFromNetwork / 1024 / 1024).toFixed(2);
    
    console.log('\nπŸ“ˆ Cache Performance Report');
    console.log('─'.repeat(40));
    console.log(`Requests: ${this.metrics.requests}`);
    console.log(`Cache hit rate: ${hitRate}%`);
    console.log(`  βœ… Hits: ${this.metrics.cacheHits}`);
    console.log(`  ❌ Misses: ${this.metrics.cacheMisses}`);
    console.log(`\nData transfer:`);
    console.log(`  πŸ’Ύ From cache: ${mbFromCache}MB`);
    console.log(`  🌐 From network: ${mbFromNetwork}MB`);
    console.log(`  πŸ’° Bandwidth saved: ${mbFromCache}MB`);
    console.log(`\nPerformance:`);
    console.log(`  ⚑ Avg cache time: ${this.metrics.avgCacheTime.toFixed(0)}ms`);
    console.log(`  🐌 Avg network time: ${this.metrics.avgNetworkTime.toFixed(0)}ms`);
    console.log(`  πŸš€ Speed improvement: ${
      this.metrics.avgNetworkTime > 0 ? 
      (this.metrics.avgNetworkTime / this.metrics.avgCacheTime).toFixed(1) : 0
    }x`);
    
    // Recommendations based on metrics
    if (hitRate < 50) {
      console.log('\n⚠️ Low cache hit rate detected!');
      console.log('  Consider increasing cache durations');
    }
    
    if (this.metrics.avgNetworkTime > 1000) {
      console.log('\n⚠️ Slow network responses!');
      console.log('  Consider using a CDN');
    }
  }
  
  stop() {
    clearInterval(this.reportInterval);
  }
}

// Start monitoring
const cacheMonitor = new CachePerformanceMonitor();
console.log('Monitoring cache performance for 30 seconds...');

// Stop after 30 seconds
setTimeout(() => {
  cacheMonitor.stop();
  console.log('\nβœ… Monitoring complete');
}, 30000);

3. Cache Invalidation Detector

// Detect cache invalidation issues
function detectCacheInvalidation() {
  console.log('\nπŸ”„ Cache Invalidation Analysis\n');
  
  const issues = [];
  
  // Check for common cache busting patterns
  const resources = performance.getEntriesByType('resource');
  
  // 1. Check for query string cache busting
  const queryStringResources = resources.filter(r => r.name.includes('?'));
  const versionParams = queryStringResources.filter(r => 
    /[?&](v|version|ver|_|t|timestamp|ts)=/.test(r.name)
  );
  
  if (versionParams.length > 0) {
    console.log(`πŸ“Œ Query string cache busting detected: ${versionParams.length} resources`);
    issues.push({
      type: 'query-string-busting',
      severity: 'low',
      message: 'Using query strings for cache busting (less reliable than filename versioning)'
    });
  }
  
  // 2. Check for filename versioning
  const hashedResources = resources.filter(r => 
    /[.-]([0-9a-f]{8,}|[0-9]+\.[0-9]+\.[0-9]+)[.-]/i.test(r.name)
  );
  
  console.log(`βœ… Filename versioning detected: ${hashedResources.length} resources`);
  
  // 3. Check for no-cache resources that should be cached
  const shouldBeCached = resources.filter(r => {
    const type = getResourceType(r.name);
    return ['image', 'font', 'script', 'style'].includes(type) && 
           !hashedResources.includes(r);
  });
  
  if (shouldBeCached.length > 0) {
    console.log(`\n⚠️ ${shouldBeCached.length} static resources without versioning:`);
    shouldBeCached.slice(0, 5).forEach(r => {
      console.log(`  - ${r.name.split('/').pop()}`);
    });
    
    issues.push({
      type: 'missing-versioning',
      severity: 'medium',
      message: 'Static resources without versioning may cause stale content'
    });
  }
  
  // 4. Check for ETags
  console.log('\n🏷️ ETag Analysis:');
  
  fetch(window.location.href, { method: 'HEAD' })
    .then(response => {
      const etag = response.headers.get('etag');
      if (etag) {
        console.log(`  βœ… ETag present: ${etag}`);
        
        // Check ETag format
        if (etag.startsWith('W/')) {
          console.log('  πŸ“ Weak ETag (byte-level changes ignored)');
        } else {
          console.log('  πŸ’ͺ Strong ETag (byte-perfect matching)');
        }
      } else {
        console.log('  ❌ No ETag header');
        issues.push({
          type: 'missing-etag',
          severity: 'low',
          message: 'No ETag for conditional requests'
        });
      }
    });
  
  // 5. Check for cache coordination issues
  console.log('\nπŸ” Cache Layer Coordination:');
  
  const cacheHeaders = [
    's-maxage', // CDN cache
    'max-age',  // Browser cache
    'proxy-revalidate',
    'must-revalidate'
  ];
  
  // Would need actual headers to analyze properly
  console.log('  Checking for cache layer conflicts...');
  
  // Report issues
  if (issues.length > 0) {
    console.log('\n⚠️ Cache Invalidation Issues:');
    issues.forEach(issue => {
      const icon = issue.severity === 'high' ? 'πŸ”΄' :
                  issue.severity === 'medium' ? '🟑' : '🟒';
      console.log(`\n${icon} ${issue.type}`);
      console.log(`  ${issue.message}`);
    });
  }
  
  // Best practices
  console.log('\nπŸ’‘ Cache Invalidation Best Practices:');
  console.log('1. Use filename hashing for static assets');
  console.log('2. Implement cache purging for dynamic content');
  console.log('3. Use ETags for conditional requests');
  console.log('4. Coordinate browser and CDN cache times');
  console.log('5. Monitor cache hit rates continuously');
  
  return issues;
}

detectCacheInvalidation();

Complete Cache Audit

Comprehensive Cache Assessment

// Run complete cache audit
async function completeCacheAudit() {
  console.log('πŸ—„οΈ COMPLETE CACHE AUDIT');
  console.log('═'.repeat(50));
  console.log(`URL: ${window.location.href}`);
  console.log(`Date: ${new Date().toLocaleDateString()}\n`);
  
  const audit = {
    score: 100,
    layers: {
      browser: { enabled: false, hitRate: 0 },
      cdn: { enabled: false, provider: null },
      serviceWorker: { enabled: false, strategies: [] },
      server: { enabled: false, headers: {} }
    },
    issues: [],
    opportunities: []
  };
  
  // Phase 1: Browser Cache Analysis
  console.log('🌐 PHASE 1: BROWSER CACHE');
  console.log('─'.repeat(40));
  
  const resources = performance.getEntriesByType('resource');
  const cached = resources.filter(r => r.transferSize === 0 && r.decodedBodySize > 0);
  
  audit.layers.browser.enabled = cached.length > 0;
  audit.layers.browser.hitRate = (cached.length / resources.length * 100).toFixed(1);
  
  console.log(`Cache hit rate: ${audit.layers.browser.hitRate}%`);
  console.log(`Cached resources: ${cached.length}/${resources.length}`);
  
  if (audit.layers.browser.hitRate < 50) {
    audit.issues.push('Low browser cache hit rate');
    audit.score -= 20;
  }
  
  // Phase 2: CDN Detection
  console.log('\n🌍 PHASE 2: CDN CACHE');
  console.log('─'.repeat(40));
  
  try {
    const response = await fetch(window.location.href, { method: 'HEAD' });
    
    // Check for CDN headers
    const cdnHeaders = {
      'cf-cache-status': 'Cloudflare',
      'x-cache': 'Generic CDN',
      'x-amz-cf-id': 'CloudFront',
      'x-served-by': 'Fastly'
    };
    
    for (const [header, provider] of Object.entries(cdnHeaders)) {
      const value = response.headers.get(header);
      if (value) {
        audit.layers.cdn.enabled = true;
        audit.layers.cdn.provider = provider;
        console.log(`βœ… CDN detected: ${provider}`);
        console.log(`  ${header}: ${value}`);
        break;
      }
    }
    
    if (!audit.layers.cdn.enabled) {
      console.log('❌ No CDN detected');
      audit.opportunities.push('Consider using a CDN for static assets');
      audit.score -= 10;
    }
    
  } catch (e) {
    console.log('Could not check CDN headers');
  }
  
  // Phase 3: Service Worker
  console.log('\nπŸ‘· PHASE 3: SERVICE WORKER CACHE');
  console.log('─'.repeat(40));
  
  if ('serviceWorker' in navigator) {
    const registration = await navigator.serviceWorker.getRegistration();
    audit.layers.serviceWorker.enabled = !!registration;
    
    if (registration) {
      console.log('βœ… Service Worker active');
      
      // Check cache storage
      if ('caches' in window) {
        const cacheNames = await caches.keys();
        console.log(`  Cache stores: ${cacheNames.length}`);
        audit.layers.serviceWorker.strategies = cacheNames;
      }
    } else {
      console.log('❌ No Service Worker');
      audit.opportunities.push('Implement Service Worker for offline support');
      audit.score -= 10;
    }
  }
  
  // Phase 4: Cache Headers
  console.log('\nπŸ“‹ PHASE 4: CACHE HEADERS');
  console.log('─'.repeat(40));
  
  const testResources = resources.slice(0, 5);
  let goodHeaders = 0;
  
  for (const resource of testResources) {
    try {
      const resp = await fetch(resource.name, { method: 'HEAD' });
      const cacheControl = resp.headers.get('cache-control');
      
      if (cacheControl) {
        goodHeaders++;
        const type = getResourceType(resource.name);
        console.log(`βœ… ${type}: ${cacheControl}`);
      }
    } catch (e) {
      // Skip
    }
  }
  
  audit.layers.server.enabled = goodHeaders > testResources.length / 2;
  
  if (!audit.layers.server.enabled) {
    audit.issues.push('Missing cache headers on resources');
    audit.score -= 15;
  }
  
  // Phase 5: Performance Impact
  console.log('\n⚑ PHASE 5: PERFORMANCE IMPACT');
  console.log('─'.repeat(40));
  
  const cachedTime = cached.reduce((sum, r) => sum + r.duration, 0) / cached.length || 0;
  const uncachedTime = resources
    .filter(r => !cached.includes(r))
    .reduce((sum, r) => sum + r.duration, 0) / (resources.length - cached.length) || 0;
  
  const speedup = uncachedTime > 0 ? (uncachedTime / cachedTime).toFixed(1) : 0;
  
  console.log(`Avg cached load time: ${cachedTime.toFixed(0)}ms`);
  console.log(`Avg uncached load time: ${uncachedTime.toFixed(0)}ms`);
  console.log(`Speed improvement: ${speedup}x`);
  
  // Final Score
  audit.score = Math.max(0, audit.score);
  
  console.log('\nπŸ“Š AUDIT SUMMARY');
  console.log('═'.repeat(50));
  console.log(`Cache Score: ${audit.score}/100`);
  console.log(`Active cache layers: ${
    Object.values(audit.layers).filter(l => l.enabled).length
  }/4`);
  console.log(`Issues found: ${audit.issues.length}`);
  console.log(`Opportunities: ${audit.opportunities.length}`);
  
  // Recommendations
  console.log('\nπŸ’‘ RECOMMENDATIONS:');
  
  if (audit.score < 70) {
    console.log('1. Implement proper cache headers');
  }
  if (!audit.layers.cdn.enabled) {
    console.log('2. Use a CDN for global performance');
  }
  if (!audit.layers.serviceWorker.enabled) {
    console.log('3. Add Service Worker for offline capability');
  }
  console.log('4. Use filename versioning for cache busting');
  console.log('5. Monitor cache hit rates continuously');
  console.log('6. Implement cache warming for critical resources');
  
  return audit;
}

// Run the complete audit
completeCacheAudit();

The Bottom Line

Caching is a superpower when done right, a nightmare when done wrong:

  • Every layer matters - Browser, CDN, server, application caches multiply performance
  • Cache invalidation is hard - The two hardest things in CS: naming and cache invalidation
  • Security risks are real - Cached sensitive data, shared cache poisoning
  • Monitoring is essential - Cache hit rates directly impact costs and UX
  • Configuration is complex - One wrong header breaks everything

Cache aggressively. Invalidate carefully. Monitor constantly. Your users expect instantβ€”caching delivers it.


Analyze caching instantly: Fusebox monitors all cache layers, tracks hit rates, detects invalidation issues, and optimizes cache strategies while you browse. Master web performance. $29 one-time purchase.