Skip to main content
Beta: Front-End Checklist is currently in beta. Some issues are still being fixed. Thanks for your patience.

Enable browser caching

Cache-Control and ETag headers are properly configured for static resources.

Utilities
Quick take
Typical fix time 20 min
  • Use immutable caching for hashed assets (1 year max-age)
  • Use short cache + revalidation for HTML pages
  • ETags enable efficient cache validation
  • stale-while-revalidate improves perceived performance
Why it matters: Proper caching eliminates redundant downloads on repeat visits—returning users see near-instant page loads when assets are served from browser cache.

Rule Details

Browser caching stores resources locally to eliminate repeated downloads.

Code Example

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        // Hashed static assets
        source: '/_next/static/:path*',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable',
          },
        ],
      },
      {
        // Images
        source: '/images/:path*',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=2592000, stale-while-revalidate=86400',
          },
        ],
      },
      {
        // HTML pages - always revalidate
        source: '/:path*',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=0, must-revalidate',
          },
        ],
      },
    ]
  },
}

Why It Matters

Proper caching eliminates redundant downloads on repeat visits—returning users see near-instant page loads when assets are served from browser cache.

Cache Header Strategy

Resource TypeCache-ControlDuration
Hashed assets (CSS, JS)public, max-age=31536000, immutable1 year
Imagespublic, max-age=259200030 days
Fontspublic, max-age=31536000, immutable1 year
HTML pagespublic, max-age=0, must-revalidateAlways check
API responsesprivate, max-age=0, no-storeNo cache

Cache-Control Directives

DirectivePurpose
publicCan be cached by browsers and CDNs
privateOnly browser can cache (sensitive data)
max-age=NCache valid for N seconds
immutableResource won't change (use with hashed filenames)
must-revalidateCheck with server when stale
no-cacheAlways revalidate before using
no-storeNever cache (sensitive data)
stale-while-revalidate=NServe stale, fetch fresh in background

Nginx Configuration

# Static assets with hash in filename
location ~* \.[a-f0-9]{8,}\.(js|css)$ {
    expires 1y;
    add_header Cache-Control "public, max-age=31536000, immutable";
}
 
# Images
location ~* \.(jpg|jpeg|png|gif|webp|avif|svg|ico)$ {
    expires 30d;
    add_header Cache-Control "public, max-age=2592000, stale-while-revalidate=86400";
}
 
# Fonts
location ~* \.(woff2?|ttf|otf|eot)$ {
    expires 1y;
    add_header Cache-Control "public, max-age=31536000, immutable";
}
 
# HTML - always revalidate
location ~* \.html$ {
    expires -1;
    add_header Cache-Control "public, max-age=0, must-revalidate";
}

Express.js Configuration

import express from 'express'
 
const app = express()
 
// Serve static files with caching
app.use('/static', express.static('public', {
  maxAge: '1y',
  immutable: true,
  etag: true,
  lastModified: true,
}))
 
// Custom middleware for fine-grained control
app.use((req, res, next) => {
  // Set cache headers based on file type
  const ext = req.path.split('.').pop()
 
  if (['js', 'css'].includes(ext) && req.path.includes('.')) {
    res.set('Cache-Control', 'public, max-age=31536000, immutable')
  } else if (['jpg', 'png', 'webp', 'svg'].includes(ext)) {
    res.set('Cache-Control', 'public, max-age=2592000')
  }
 
  next()
})

ETags for Cache Validation

// Express.js ETag configuration
import express from 'express'
 
const app = express()
 
// Enable strong ETags
app.set('etag', 'strong')
 
// How ETags work:
// 1. Server sends: ETag: "abc123"
// 2. Browser caches response
// 3. On next request, browser sends: If-None-Match: "abc123"
// 4. If resource unchanged, server responds: 304 Not Modified
// 5. Browser uses cached version (no body transferred)

Service Worker Caching

// sw.js - Advanced caching strategies
const CACHE_NAME = 'v1'
const STATIC_ASSETS = ['/app.js', '/styles.css', '/logo.svg']
 
// Cache on install
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => cache.addAll(STATIC_ASSETS))
  )
})
 
// Stale-while-revalidate strategy
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(cached => {
      const fetchPromise = fetch(event.request).then(response => {
        // Update cache
        caches.open(CACHE_NAME).then(cache => {
          cache.put(event.request, response.clone())
        })
        return response
      })
 
      // Return cached immediately, update in background
      return cached || fetchPromise
    })
  )
})

Verifying Cache Headers

// Check cache headers in browser
async function verifyCaching(url) {
  const response = await fetch(url, { method: 'HEAD' })
 
  const headers = {
    'cache-control': response.headers.get('cache-control'),
    'etag': response.headers.get('etag'),
    'last-modified': response.headers.get('last-modified'),
    'expires': response.headers.get('expires'),
  }
 
  console.table(headers)
  return headers
}
 
// In DevTools Network tab:
// - Size column shows "(disk cache)" or "(memory cache)" for cached resources
// - Time column shows fast load times for cached resources

Standards

  • Use web.dev: Learn Performance as the standard for measuring the final production behavior, not just local synthetic output.
  • Use Chrome Developers: Lighthouse overview as the standard for measuring the final production behavior, not just local synthetic output.

Support Notes

  • Caching behavior depends on real cache headers, intermediary caches, and revalidation paths, so confirm the final network behavior in production-like conditions.
  • Do not assume origin configuration equals browser behavior when a CDN or service worker can change the effective caching path.

Verification

Automated Checks

  • Check Network tab—look for "(disk cache)" or "(memory cache)"
  • Verify Cache-Control headers in response
  • Use WebPageTest to compare first vs repeat view

Manual Checks

  • Test repeat visits—assets should load instantly
  • Check service worker caching in Application tab

Use with AI

Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.

Check

Verify implementation

Verify that proper cache headers are set for static resources to enable browser caching.

Fix

Auto-fix issues

Configure appropriate Cache-Control and ETag headers for different resource types.

Explain

Learn more

Explain how browser caching reduces server requests and improves repeat visit performance.

Review

Code review

Review the routes, assets, and loading behavior that affect Enable browser caching. Flag exact files, requests, or rendering steps that add unnecessary network, CPU, or layout cost, and describe the measurement method used to confirm the issue.

Sources

References used to support the guidance in this rule.

Further Reading

Tools and supplementary material for exploring the topic in more depth.

PageSpeed Insights
pagespeed.web.devTool

Rules that often go hand-in-hand with this one.

Use a content delivery network

Static assets are served from a CDN for reduced latency and faster delivery.

Performance
Register a service worker for caching and offline support

A service worker is registered to intercept network requests, cache critical assets, and enable offline functionality for your web application.

Performance
Convert animated GIFs to video

Large animated GIFs are replaced with more efficient video formats like MP4 or WebM to reduce page weight.

Performance

Was this rule helpful?

Your feedback helps improve rule quality. This stays internal for now.

Loading feedback...
0 / 385