Browser Caching & Cache Policy Issues | Blue Frog Docs

Browser Caching & Cache Policy Issues

Diagnose and fix browser caching problems to improve repeat visit performance and reduce server load

Browser Caching & Cache Policy Issues

What This Means

Browser caching allows web browsers to store copies of static resources (images, CSS, JavaScript, fonts) locally, so they don't need to be re-downloaded on subsequent visits. Poor cache configuration forces browsers to re-download resources unnecessarily, wasting bandwidth and slowing down repeat visits.

Cache Policy Types

Cache-Control Directives:

  • max-age=31536000 - Cache for 1 year (recommended for static assets)
  • no-cache - Validate with server before using cached version
  • no-store - Never cache (sensitive data)
  • public - Can be cached by browsers and CDNs
  • private - Only browser can cache (not CDNs)
  • immutable - Resource will never change (perfect for hashed filenames)

Common Cache Durations:

  • Static assets (versioned): 1 year (31536000 seconds)
  • Images: 1 week to 1 year (604800-31536000 seconds)
  • HTML: 0 seconds to 1 hour (0-3600 seconds)
  • API responses: Varies (often no-cache or short duration)

Impact on Your Business

Performance Impact:

  • Poor caching = slower repeat visits
  • Every resource re-downloaded wastes time
  • Mobile users especially affected (limited data)
  • Increased server load and bandwidth costs

User Experience:

  • Fast initial load but slow repeat visits
  • Unnecessary loading indicators
  • Wasted mobile data
  • Poor perceived performance

Business Costs:

  • Higher bandwidth bills
  • Increased CDN costs
  • More server resources needed
  • Reduced conversion on repeat visits

Core Web Vitals:

How to Diagnose

  1. Open Chrome DevTools (F12)
  2. Navigate to "Network" tab
  3. Check "Disable cache" is UNCHECKED
  4. Reload page (first visit)
  5. Reload page again (second visit)
  6. Look at "Size" column:
    • (disk cache) or (memory cache) = cached properly
    • File size (e.g., "45.2 KB") = not cached, re-downloaded

Advanced Analysis:

  1. Click on any resource
  2. View "Headers" tab
  3. Check "Response Headers" for:
    cache-control: max-age=31536000
    
  4. Check "Request Headers" for:
    if-modified-since: [date]
    if-none-match: [etag]
    

What to Look For:

  • Resources downloading on second visit (should be cached)
  • Missing or short max-age values
  • no-cache or no-store on static resources
  • No cache-control header at all
  • 200 status (re-download) instead of 304 (not modified)

Method 2: Lighthouse Audit

  1. Open Chrome DevTools (F12)
  2. Navigate to "Lighthouse" tab
  3. Run Performance audit
  4. Look for "Serve static assets with an efficient cache policy"

What to Look For:

  • List of resources with poor cache policies
  • Recommended cache duration for each
  • Potential savings in bytes and requests
  • Number of resources affected

Method 3: PageSpeed Insights

  1. Visit PageSpeed Insights
  2. Enter your URL
  3. Review diagnostics section
  4. Look for "Serve static assets with an efficient cache policy"

What to Look For:

  • Resources with short cache duration
  • Total size of resources not cached properly
  • Specific URLs and recommended cache times
  • Mobile vs desktop differences

Method 4: WebPageTest

  1. Visit WebPageTest.org
  2. Enter your URL
  3. Run test
  4. Look at:
    • "First View" vs "Repeat View" performance
    • Large difference indicates caching issues
    • Content breakdown showing cache headers

What to Look For:

  • Minimal improvement from first to repeat view
  • Resources downloading on repeat view
  • Missing cache headers
  • Grade for "Cache static content"

Method 5: Manual Header Check

Use curl to check cache headers:

# Check cache headers for an image
curl -I https://example.com/image.jpg

# Look for:
# Cache-Control: max-age=31536000
# ETag: "abc123"
# Last-Modified: [date]

What to Look For:

  • Presence of Cache-Control header
  • Appropriate max-age value
  • ETag or Last-Modified for validation
  • Correct directives (public vs private)

General Fixes

Fix 1: Set Long Cache for Static Assets

Configure proper cache headers:

  1. Apache (.htaccess):

    <IfModule mod_expires.c>
      ExpiresActive On
    
      # Images
      ExpiresByType image/jpeg "access plus 1 year"
      ExpiresByType image/png "access plus 1 year"
      ExpiresByType image/gif "access plus 1 year"
      ExpiresByType image/webp "access plus 1 year"
      ExpiresByType image/svg+xml "access plus 1 year"
    
      # CSS and JavaScript
      ExpiresByType text/css "access plus 1 year"
      ExpiresByType text/javascript "access plus 1 year"
      ExpiresByType application/javascript "access plus 1 year"
    
      # Fonts
      ExpiresByType font/woff "access plus 1 year"
      ExpiresByType font/woff2 "access plus 1 year"
      ExpiresByType application/font-woff "access plus 1 year"
    
      # HTML - short cache
      ExpiresByType text/html "access plus 0 seconds"
    </IfModule>
    
    # Cache-Control headers
    <IfModule mod_headers.c>
      <FilesMatch "\.(ico|jpg|jpeg|png|gif|webp|svg|css|js|woff|woff2)$">
        Header set Cache-Control "public, max-age=31536000, immutable"
      </FilesMatch>
    
      <FilesMatch "\.(html|htm)$">
        Header set Cache-Control "no-cache, must-revalidate"
      </FilesMatch>
    </IfModule>
    
  2. Nginx (nginx.conf):

    # Cache static assets
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|webp|woff|woff2)$ {
      expires 1y;
      add_header Cache-Control "public, max-age=31536000, immutable";
    }
    
    # HTML - no cache
    location ~* \.(html|htm)$ {
      expires -1;
      add_header Cache-Control "no-cache, must-revalidate";
    }
    
    # API responses - no cache
    location /api/ {
      add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate";
    }
    
  3. Express.js (Node.js):

    const express = require('express');
    const app = express();
    
    // Serve static files with cache
    app.use('/static', express.static('public', {
      maxAge: '1y',
      immutable: true
    }));
    
    // HTML files - no cache
    app.get('*.html', (req, res) => {
      res.set('Cache-Control', 'no-cache, must-revalidate');
      res.sendFile(__dirname + req.path);
    });
    
  4. Next.js (next.config.js):

    module.exports = {
      async headers() {
        return [
          {
            source: '/static/:path*',
            headers: [
              {
                key: 'Cache-Control',
                value: 'public, max-age=31536000, immutable',
              },
            ],
          },
          {
            source: '/:path*.html',
            headers: [
              {
                key: 'Cache-Control',
                value: 'no-cache, must-revalidate',
              },
            ],
          },
        ];
      },
    };
    

Fix 2: Implement Cache Busting

Ensure users get updates when files change:

  1. Versioned filenames (recommended):

    <!-- Build tools add hash to filename -->
    <link rel="stylesheet" href="styles.a8f3d2e1.css">
    <script src="app.b7c4e9f2.js"></script>
    

    Build configuration (Webpack):

    module.exports = {
      output: {
        filename: '[name].[contenthash].js',
        chunkFilename: '[name].[contenthash].js'
      }
    };
    
  2. Query string versioning:

    <!-- Add version to query string -->
    <link rel="stylesheet" href="styles.css?v=1.2.3">
    <script src="app.js?v=1.2.3"></script>
    
  3. Automatic versioning with build tools:

    // Vite automatically adds hashes
    import './style.css';  // Becomes style.abc123.css in build
    

Fix 3: Use ETags and Validation

Allow conditional requests:

  1. Apache - Enable ETags:

    # .htaccess
    FileETag MTime Size
    
    <IfModule mod_headers.c>
      Header set ETag "%{REQUEST_TIME}x-%{REQUEST_SIZE}x"
    </IfModule>
    
  2. Nginx - Enable ETags:

    # ETags enabled by default
    # Ensure not disabled
    etag on;
    
  3. Handle 304 Not Modified responses:

    // Express.js example
    app.get('/data.json', (req, res) => {
      const etag = generateETag(data);
    
      if (req.headers['if-none-match'] === etag) {
        res.status(304).end();
        return;
      }
    
      res.set('ETag', etag);
      res.set('Cache-Control', 'public, max-age=3600');
      res.json(data);
    });
    

Fix 4: Optimize Cache Strategy by Resource Type

Different resources need different strategies:

  1. Static assets (versioned filenames):

    Cache-Control: public, max-age=31536000, immutable
    
  2. Images without versioning:

    Cache-Control: public, max-age=2592000
    (30 days)
    
  3. HTML pages:

    Cache-Control: no-cache
    (always validate with server)
    
  4. API responses (data that changes):

    Cache-Control: no-cache, must-revalidate
    
  5. User-specific content:

    Cache-Control: private, max-age=3600
    (cache in browser only, not CDNs)
    
  6. Sensitive data:

    Cache-Control: no-store
    (never cache)
    

Fix 5: Leverage CDN Caching

CDN can serve cached content globally:

  1. Set appropriate cache headers for CDN:

    Cache-Control: public, max-age=31536000
    CDN-Cache-Control: max-age=86400
    
  2. Use CDN features:

    • Enable automatic cache headers
    • Set up cache purge/invalidation
    • Configure cache keys properly
    • Use edge caching for API responses
  3. Cloudflare example:

    // Cloudflare Workers
    addEventListener('fetch', event => {
      event.respondWith(handleRequest(event.request));
    });
    
    async function handleRequest(request) {
      const cache = caches.default;
      let response = await cache.match(request);
    
      if (!response) {
        response = await fetch(request);
        response = new Response(response.body, response);
        response.headers.set('Cache-Control', 'public, max-age=86400');
        event.waitUntil(cache.put(request, response.clone()));
      }
    
      return response;
    }
    

Fix 6: Implement Service Worker Caching

Advanced caching with Service Workers:

  1. Cache static assets:

    // service-worker.js
    const CACHE_NAME = 'v1';
    const STATIC_ASSETS = [
      '/',
      '/styles.css',
      '/app.js',
      '/logo.png'
    ];
    
    // Install - cache static assets
    self.addEventListener('install', event => {
      event.waitUntil(
        caches.open(CACHE_NAME).then(cache => {
          return cache.addAll(STATIC_ASSETS);
        })
      );
    });
    
    // Fetch - serve from cache, fallback to network
    self.addEventListener('fetch', event => {
      event.respondWith(
        caches.match(event.request).then(response => {
          return response || fetch(event.request);
        })
      );
    });
    
  2. Network first with cache fallback:

    self.addEventListener('fetch', event => {
      event.respondWith(
        fetch(event.request)
          .then(response => {
            // Update cache with fresh response
            const responseClone = response.clone();
            caches.open(CACHE_NAME).then(cache => {
              cache.put(event.request, responseClone);
            });
            return response;
          })
          .catch(() => {
            // Network failed, try cache
            return caches.match(event.request);
          })
      );
    });
    
  3. Stale-while-revalidate:

    self.addEventListener('fetch', event => {
      event.respondWith(
        caches.open(CACHE_NAME).then(cache => {
          return cache.match(event.request).then(response => {
            const fetchPromise = fetch(event.request).then(networkResponse => {
              cache.put(event.request, networkResponse.clone());
              return networkResponse;
            });
    
            // Return cached version immediately, update in background
            return response || fetchPromise;
          });
        })
      );
    });
    

Fix 7: Avoid Common Caching Mistakes

Don't cache what shouldn't be cached:

  1. Don't cache HTML too long:

    ✗ Cache-Control: max-age=31536000
    ✓ Cache-Control: no-cache
    
  2. Don't cache user-specific content publicly:

    ✗ Cache-Control: public, max-age=3600
    ✓ Cache-Control: private, max-age=3600
    
  3. Don't cache without validation for dynamic content:

    ✗ Cache-Control: max-age=86400
    ✓ Cache-Control: max-age=86400, must-revalidate
    
  4. Don't forget to version static assets:

    ✗ <link rel="stylesheet" href="styles.css">
    ✓ <link rel="stylesheet" href="styles.v2.css">
    ✓ <link rel="stylesheet" href="styles.abc123.css">
    

Platform-Specific Guides

Detailed implementation instructions for your specific platform:

Platform Troubleshooting Guide
Shopify Shopify Caching Guide
WordPress WordPress Caching Guide
Wix Wix Caching Guide
Squarespace Squarespace Caching Guide
Webflow Webflow Caching Guide

Verification

After implementing caching:

  1. Test in Chrome DevTools:

    • Load page fresh (hard reload: Cmd/Ctrl + Shift + R)
    • Reload normally (Cmd/Ctrl + R)
    • Check Network tab for "(disk cache)"
    • Verify response headers show correct cache-control
  2. Run Lighthouse:

    • Should pass "Serve static assets with an efficient cache policy"
    • Check potential savings (should be minimal)
  3. Test cache busting:

    • Update a CSS or JS file
    • Deploy with new version/hash
    • Verify users get new version
    • Old version still cached
  4. Test repeat visits:

    • Use WebPageTest.org
    • Compare "First View" vs "Repeat View"
    • Repeat view should be significantly faster
    • Most resources from cache
  5. Monitor in production:

    • Check CDN cache hit rates
    • Monitor bandwidth usage (should decrease)
    • Check server load (should decrease)
    • Verify real user performance improves

Common Mistakes

  1. Caching HTML too long - Users don't get updates
  2. Not versioning static files - Can't change cached files
  3. Using no-cache on static assets - Defeats purpose
  4. Inconsistent cache headers - Different headers for same file
  5. Forgetting mobile users - Poor caching wastes mobile data
  6. Not testing cache behavior - Assumes it works without verification
  7. Caching errors - 404 or 500 pages cached
  8. Ignoring CDN cache - Only focusing on browser cache
  9. No cache invalidation strategy - Can't force updates when needed
  10. Caching user-specific content publicly - Privacy and correctness issues

Additional Resources

// SYS.FOOTER