Effective cache management is crucial for web application performance and ensuring users receive the latest updates. This guide covers cache busting strategies and performance optimization techniques.
Why Cache Management Matters
The Cache Dilemma
-
Performance vs. Freshness: Caching improves load times but can serve stale content
-
User Experience: Cached content loads faster but may not reflect recent changes
-
CDN Efficiency: Content delivery networks rely on aggressive caching for global performance
-
Development Challenges: Developers need to ensure updates reach users promptly
Common Cache Problems
-
Stale Content: Users see outdated versions of your site
-
Mixed Versions: Some files update while others remain cached
-
Development Confusion: Changes don’t appear immediately during testing
-
Deployment Issues: Production updates don’t propagate to users
Cache Busting Strategies
1. Version-Based Cache Busting
Add version parameters to asset URLs:
<!-- CSS with version parameter -->
<link rel="stylesheet" href="/assets/main.css?v=1.2.3" />
<!-- JavaScript with version parameter -->
<script src="/assets/app.js?v=1.2.3"></script>
<!-- Images with cache busting -->
<img src="/images/logo.png?v=1.2.3" alt="Logo" />
Pros:
- Simple to implement
- Works with any asset type
- Easy to understand and debug
Cons:
- Manual version management
- All assets invalidated together
- Query parameters may be ignored by some proxies
2. File Hash-Based Cache Busting
Use content hashes in filenames:
<!-- CSS with content hash -->
<link rel="stylesheet" href="/assets/main-a1b2c3d4.css" />
<!-- JavaScript with content hash -->
<script src="/assets/app-e5f6g7h8.js"></script>
Pros:
- Automatic invalidation when content changes
- Granular cache control per file
- More reliable than query parameters
Cons:
- Requires build process integration
- More complex deployment
- Need to update references
3. Directory-Based Versioning
Use version numbers in directory paths:
<!-- Versioned directory structure -->
<link rel="stylesheet" href="/v1.2.3/assets/main.css" />
<script src="/v1.2.3/assets/app.js"></script>
Pros:
- Clean separation of versions
- Easy rollback capability
- Clear version identification
Cons:
- Increased storage requirements
- Complex deployment process
- Manual path management
Jekyll-Specific Cache Busting
Site Variables for Cache Busting
# _config.yml
version: "1.2.3"
cache_bust: 20240130-143022
<!-- In templates -->
<link rel="stylesheet" href="/assets/main.css?v=1.0.0" />
<script src="/assets/app.js?v=true"></script>
Build-Time Cache Busting
# _config.yml
plugins:
- jekyll-cache-bust
cache_bust:
enabled: true
strategy: "content_hash"
Liquid Template Cache Busting
<link rel="stylesheet" href="/assets/main.css?v=1760036520" />
# .htaccess for Apache
<IfModule mod_expires.c>
ExpiresActive on
# CSS and JavaScript (1 year with versioning)
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
# Images (1 month)
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/jpg "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/svg+xml "access plus 1 month"
# HTML (1 hour for dynamic content)
ExpiresByType text/html "access plus 1 hour"
</IfModule>
# Cache control headers
<IfModule mod_headers.c>
# Versioned assets - cache aggressively
<FilesMatch "\.(css|js|png|jpg|jpeg|gif|svg)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
# HTML - short cache with revalidation
<FilesMatch "\.html$">
Header set Cache-Control "public, max-age=3600, must-revalidate"
</FilesMatch>
</IfModule>
Nginx Configuration
# nginx.conf
location ~* \.(css|js|png|jpg|jpeg|gif|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary Accept-Encoding;
}
location ~* \.html$ {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}
<!-- Prevent caching of HTML pages -->
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<!-- Or allow short-term caching -->
<meta http-equiv="Cache-Control" content="public, max-age=3600" />
CDN Cache Busting
<!-- Version in CDN URLs -->
<script src="https://cdn.example.com/v1.2.3/app.js"></script>
<!-- Hash-based CDN URLs -->
<link rel="stylesheet" href="https://cdn.example.com/main-a1b2c3d4.css" />
Preloading Critical Assets
<!-- Preload critical CSS -->
<link rel="preload" href="/assets/critical.css?v=1.2.3" as="style" />
<!-- Preload important JavaScript -->
<link rel="preload" href="/assets/app.js?v=1.2.3" as="script" />
<!-- Preload hero images -->
<link rel="preload" href="/images/hero.jpg?v=1.2.3" as="image" />
Resource Hints
<!-- DNS prefetch for external resources -->
<link rel="dns-prefetch" href="//cdn.example.com" />
<!-- Preconnect to external domains -->
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin />
<!-- Prefetch next page resources -->
<link rel="prefetch" href="/next-page.html" />
Development vs. Production
Development Environment
# _config_dev.yml
cache_bust: false
version: "dev"
# Disable caching in development
plugins:
- jekyll-cache-bust
cache_bust:
enabled: false
Production Environment
# _config_prod.yml
cache_bust: true
version: "1.2.3"
# Enable aggressive caching in production
plugins:
- jekyll-cache-bust
cache_bust:
enabled: true
strategy: 'content_hash'
extensions: ['css', 'js', 'png', 'jpg']
Monitoring and Testing
-
Browser DevTools: Check cache status in Network tab
-
GTmetrix: Analyze caching effectiveness
-
WebPageTest: Detailed cache analysis
-
Lighthouse: Performance auditing with cache recommendations
Testing Cache Behavior
# Test cache headers
curl -I https://yoursite.com/assets/main.css
# Test with different user agents
curl -H "User-Agent: Mozilla/5.0..." -I https://yoursite.com/
# Check cache validation
curl -H "If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT" -I https://yoursite.com/
Cache Debugging
// Check if resource was served from cache
window.addEventListener("load", function () {
const entries = performance.getEntriesByType("resource");
entries.forEach((entry) => {
if (entry.transferSize === 0) {
console.log("From cache:", entry.name);
} else {
console.log("From network:", entry.name);
}
});
});
Best Practices
Asset Organization
-
Separate Static from Dynamic: Cache static assets aggressively, dynamic content conservatively
-
Version Critical Assets: Ensure important updates reach users quickly
-
Use Immutable Caching: Set
immutable
flag for versioned assets
-
Optimize Cache Hierarchies: Consider browser, CDN, and proxy caches
Deployment Strategies
-
Atomic Deployments: Update all references simultaneously
-
Graceful Rollouts: Allow time for cache propagation
-
Rollback Preparation: Keep previous versions accessible
-
Cache Warming: Pre-populate CDN caches after deployment
-
Minimize Cache Misses: Use predictable versioning schemes
-
Optimize Cache Hit Ratio: Balance freshness with performance
-
Reduce Cache Churn: Avoid unnecessary version bumps
-
Monitor Cache Effectiveness: Track hit rates and performance impact