Skip to content

Cache Strategy & Cloudflare Configuration

Status: ✅ Deployed
Last Updated: 2026-01-23


Problem

After deployments, users would see stale content because:

  1. Service workers cached old bundles and kept serving them until browser cleared storage
  2. Cloudflare cached HTML shells for extended periods (1 hour)
  3. Browser caches combined with SW caches meant users needed incognito to see updates

Solution

1. Tighter Cache Headers (public/_headers)

HTML shells now have Cache-Control: no-cache, no-store, must-revalidate:

  • Cloudflare revalidates the HTML on every request
  • Browsers don't serve stale copies without checking

Critical non-hashed files also bypass cache:

  • /sw.js and /service-worker.js → always fetch fresh (SW updates picked up immediately)
  • /manifest.webmanifest → refreshes with new icons/config
  • /version.json → used for cache invalidation tracking

Hashed assets (/assets/*.*) stay cached aggressively (1 year) because file names change on content updates.


2. Cloudflare Cache Rules

Important: All custom domains (dev.ourlantern.app, docs.ourlantern.app, storybook, etc.) are Cloudflare Pages custom domains under the main ourlantern.app zone. Cache rules are applied once at the zone level and automatically apply to all subdomains.

Two methods to set up:

Method A: Manual (One-time setup)

bash
# For dev environment
npm run cf:setup-cache-rules:dev

# For prod environment  
npm run cf:setup-cache-rules:prod

This requires:

  • CLOUDFLARE_ZONE_ID env var (or in .env.local)
  • CLOUDFLARE_API_TOKEN env var (or in .env.local)
  • CLOUDFLARE_ACCOUNT_ID env var (or in .env.local)

The script will display the exact cache rules to create and a direct link to your Cloudflare Dashboard.

Also ensure Browser Cache TTL is set to "Respect Existing Headers" in Caching → Configuration. This lets our tight cache-control headers take effect on the browser side.

Rules created (free-tier compatible):

  1. Bypass cache for / and /index.html
  2. Bypass cache for /sw.js, /service-worker.js
  3. Bypass cache for /manifest.webmanifest, /version.json
  4. Cache 1 year for /assets/ (prefix match via starts_with)

Method B: Cloudflare Dashboard

If API setup fails or you prefer manual config:

  1. Go to Cloudflare DashboardCachingCache Rules
  2. Create rules in this order:
RuleWhenThen
HTML Shell (Root & Index)(http.request.uri.path eq "/" or http.request.uri.path eq "/index.html")Bypass Cache
Service Workers(http.request.uri.path eq "/sw.js" or http.request.uri.path eq "/service-worker.js")Bypass Cache
Manifest & Version(http.request.uri.path eq "/manifest.webmanifest" or http.request.uri.path eq "/version.json")Bypass Cache
Hashed Assetsstarts_with(http.request.uri.path, "/assets/")Cache (TTL: 1 year)

3. Automated Cache Purge on Deploy

Both CI workflows now purge critical files after deployment:

Deployed URLs:

  • .github/workflows/deploy-dev.yml → purges https://dev.ourlantern.app/*
  • .github/workflows/deploy-prod.yml → purges https://ourlantern.app/*

Files purged after every deploy:

  • / (root)
  • /index.html
  • /sw.js
  • /service-worker.js
  • /manifest.webmanifest
  • /version.json

This ensures:

  • Hashed assets remain cached (unchanged filename = no purge needed)
  • HTML shells are always fresh
  • Service workers check for updates on next visit
  • New deployments are live within minutes

Browser-Side Clearing (For Users/Testing)

If a user still sees old content after deployment:

  1. Open DevToolsApplication tab
  2. Service Workers → Click Unregister
  3. Clear Storage → Select all storage types → Clear Site Data
  4. Hard refresh (Cmd+Shift+R or Ctrl+Shift+R)
  5. Alternatively: Incognito window to test clean state

Verification Checklist

After setup, verify the configuration:

bash
# Check _headers is correct
cat public/_headers | grep -A 5 "HTML shell"

# Verify deployment purges cache (after next deploy)
# Check CI logs for: "✅ Cache purge complete"

# Test cache headers (should show no-cache for HTML)
curl -I https://dev.ourlantern.app/index.html
# Look for: Cache-Control: no-cache, no-store, must-revalidate

curl -I https://dev.ourlantern.app/assets/main-*.js
# Look for: Cache-Control: public, max-age=31536000, immutable

What This Fixes

✅ Users see updates without manual storage clearing
✅ Service worker updates deploy immediately
✅ Hashed assets stay cached (browser performance preserved)
✅ HTML shells always reflect latest deployment
✅ Cloudflare + browser caches work together (not against each other)


Files Modified


References

Built with VitePress