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.