Cache Strategy
Purpose: Explain Lantern's multi-layered cache strategy for optimal performance and instant updates.
Overview
Lantern uses a three-tier cache strategy to balance performance with fresh content delivery:
- Cache Busting - Immutable assets with unique filenames
- Short TTL for HTML - 5-minute cache window for quick updates
- Emergency Purge - Manual purge script for critical situations
Layer 1: Cache Busting (Primary Strategy)
Implementation
Build-time filename generation with unique build IDs:
javascript
// vite.config.mjs
build: {
rollupOptions: {
output: {
entryFileNames: `assets/[name]-${buildId}.[hash].js`,
chunkFileNames: `assets/[name]-${buildId}.[hash].js`,
assetFileNames: `assets/[name]-${buildId}.[hash][extname]`,
},
},
}Build ID generation (scripts/generate-version.mjs):
- Creates
public/version.jsonwith unique build ID - Build ID format:
{timestamp}-{commit-sha-8} - Never cached by Cloudflare (
Cache-Control: no-cache)
Result
- JS/CSS assets: Cached forever (
max-age=31536000, immutable) - New deployment: New filenames = automatic cache bypass
- Zero stale assets: Changed files always have new URLs
Example:
Old: assets/main-20260120-abc12345.js
New: assets/main-20260122-def67890.jsLayer 2: Short TTL for HTML
Configuration
/*.html
Cache-Control: public, max-age=300, stale-while-revalidate=60, must-revalidate
/
Cache-Control: public, max-age=300, stale-while-revalidate=60, must-revalidate
/index.html
Cache-Control: public, max-age=300, stale-while-revalidate=60, must-revalidateBehavior
- max-age=300: HTML cached for 5 minutes at CDN edge
- stale-while-revalidate=60: Serve stale content while fetching fresh (better UX)
- must-revalidate: Force revalidation after expiry
Why 5 Minutes?
- Performance: Reduces origin hits for high-traffic periods
- Freshness: Users get updates within 5 minutes
- Balance: Good middle ground between instant updates (0s) and long caching (hours)
Layer 3: Purge on Deploy (Automatic)
Implementation
GitHub Actions (.github/workflows/deploy-dev.yml, .github/workflows/deploy-prod.yml):
yaml
- name: Purge Cloudflare Cache
run: |
curl -X POST "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/purge_cache" \
-H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \
-H "Content-Type: application/json" \
--data '{"files":["https://ourlantern.app/","https://ourlantern.app/index.html"]}'What Gets Purged
Only HTML files:
/(root)/index.html
Not purged:
- JS/CSS assets (cache busting handles these)
- Images/fonts (static, rarely change)
- Service worker (already has
no-cacheheaders)
Why Selective Purging?
- Efficiency: Only purge what needs updating
- Cost: Cloudflare has purge rate limits
- Performance: Keep static assets cached globally
Layer 4: Emergency Full Purge (Manual)
When to Use
Use full purge ONLY when:
- Cache busting isn't working (deployment issue)
- Wrong content served globally
- Critical bug requiring immediate flush
- Something is "on fire"
DO NOT use for:
- Normal deployments (automatic purge handles this)
- Minor updates (5-minute TTL handles this)
- Testing (use
?cacheBust=123query param)
Usage
bash
# Purge dev environment
./scripts/purge-cache.sh dev
# Purge production
./scripts/purge-cache.sh prod
# Nuclear option - purge everything
./scripts/purge-cache.sh allPrerequisites
Add to .env.local:
bash
CLOUDFLARE_ZONE_ID=your_zone_id_here
CLOUDFLARE_API_TOKEN=your_api_token_hereGet credentials:
- Zone ID: Cloudflare Dashboard → Your Domain → Overview (right sidebar)
- API Token: Cloudflare Dashboard → My Profile → API Tokens
- Required permission:
Zone.Cache Purge
- Required permission:
What Gets Purged
Everything:
- All HTML files
- All JS/CSS assets
- All images, fonts, icons
- All cached API responses
- Everything Cloudflare has cached
Impact
- Users: Slower load times until cache rebuilds
- Origin: Higher server load temporarily
- Cloudflare: May trigger rate limits if done repeatedly
Cache Flow Diagram
┌─────────────────────────────────────────────────────────┐
│ 1. User visits ourlantern.app │
└────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 2. Cloudflare CDN checks cache for index.html │
│ • Fresh (< 5 min): Serve from cache │
│ • Stale (> 5 min): Fetch new, serve old while wait │
└────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 3. HTML references assets with build IDs │
│ <script src="/assets/main-20260122-abc123.js"> │
└────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 4. CDN checks cache for JS/CSS │
│ • Hit: Serve from cache (1 year max-age) │
│ • Miss: Fetch from origin, cache for 1 year │
└────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 5. App loads and checks version.json │
│ • Same version: Continue normally │
│ • New version: Auto-reload app │
└─────────────────────────────────────────────────────────┘Deployment Flow
┌─────────────────────────────────────────────────────────┐
│ 1. Developer pushes to main │
└────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 2. CI/CD builds app │
│ • Generates new build ID │
│ • Creates version.json │
│ • Builds assets with new filenames │
└────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 3. Deploys to Cloudflare Pages │
│ • dist/ uploaded to CDN │
└────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 4. Purges HTML from cache │
│ • / and /index.html only │
│ • Forces fresh HTML on next visit │
└────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 5. Users get new version │
│ • Next visit: Fresh HTML (purged) │
│ • HTML loads new JS/CSS (new filenames) │
│ • Old assets ignored (no references) │
└─────────────────────────────────────────────────────────┘Cache Headers Reference
Static Assets (JS/CSS/Images)
/assets/*
Cache-Control: public, max-age=31536000, immutable- public: Can be cached by CDN
- max-age=31536000: Cache for 1 year
- immutable: Browser won't revalidate (trusts it never changes)
HTML Files
/*.html
Cache-Control: public, max-age=300, stale-while-revalidate=60, must-revalidate- public: Can be cached by CDN
- max-age=300: Cache for 5 minutes
- stale-while-revalidate=60: Serve stale + fetch fresh in background
- must-revalidate: Force check after expiry
Service Worker
/sw.js
Cache-Control: no-cache, no-store, must-revalidate- no-cache: Always revalidate with origin
- no-store: Never cache
- must-revalidate: Force revalidation
Version File
/version.json
Cache-Control: no-cache, no-store, must-revalidateSame as service worker - always fetch fresh for version checks.
Troubleshooting
Users seeing old version after deployment
Likely causes:
- 5-minute HTML cache hasn't expired yet
- Browser cache (rare with proper headers)
- Service worker cache (should auto-update)
Solutions:
- Wait 5 minutes for automatic expiry
- Run
./scripts/purge-cache.sh prodfor immediate update - User hard refresh:
Ctrl+Shift+R/Cmd+Shift+R
Assets returning 404 after deployment
Likely causes:
- Build failed to generate assets
- Deployment uploaded incomplete dist/
- Old HTML referencing new assets (timing issue)
Solutions:
- Check GitHub Actions logs for build errors
- Verify dist/ contains all assets before deploy
- Run
./scripts/purge-cache.sh prodto clear stale HTML
High origin bandwidth usage
Likely causes:
- Cache headers misconfigured
- Excessive cache purging
- CDN not caching properly
Solutions:
- Verify headers:
curl -I https://ourlantern.app - Check Cloudflare Analytics → Caching
- Reduce purge frequency if needed
Best Practices
DO ✅
- Use cache busting for all versioned assets
- Keep HTML TTL short (5-10 minutes)
- Purge selectively (HTML only) on deploy
- Monitor cache hit rates in Cloudflare
- Test deployments in dev environment first
DON'T ❌
- Don't cache HTML for hours (stale references)
- Don't purge everything on every deploy (wasteful)
- Don't rely solely on query params for cache busting
- Don't cache service workers
- Don't skip version.json generation
Related Documentation
- PWA Architecture - Service worker caching strategy
- Deployment Guide - Full deployment process
- CI/CD Guide - Automated deployment pipeline
- Cloudflare Setup - Domain and access configuration
Metrics to Monitor
Cloudflare Analytics:
- Cache hit rate (target: >95% for assets)
- Bandwidth savings (target: >80% from cache)
- Origin requests (should be low)
Performance:
- Time to First Byte (TTFB)
- Largest Contentful Paint (LCP)
- Cache invalidation latency
Version checks:
- Auto-reload frequency
- Service worker update success rate