Skip to content
intermediatePhase 38 · Web Performance

Browser Caching

Leverage HTTP caching headers, service workers, and cache strategies.

45m
0 problems
Topic Progress0%

HTTP Caching Headers

HTTP Caching Headers

Control how browsers cache responses.

Cache-Control

# Cache for 1 year (immutable assets)
Cache-Control: public, max-age=31536000, immutable

# Cache for 1 hour
Cache-Control: public, max-age=3600

# No caching
Cache-Control: no-cache, no-store, must-revalidate

# Revalidate before using cache
Cache-Control: no-cache

ETag and Last-Modified

# Server response
ETag: "abc123"
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT

# Client conditional request
If-None-Match: "abc123"
If-Modified-Since: Wed, 21 Oct 2023 07:28:00 GMT

# Server response if not modified
304 Not Modified

Vary Header

# Cache different versions based on headers
Vary: Accept-Encoding
Vary: Accept-Language
Vary: Authorization

Content Hashing

// Webpack config
module.exports = {
  output: {
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].js',
  },
};

// HTML template
<script src="main.a1b2c3d4.js"></script>
<link rel="stylesheet" href="styles.e5f6g7h8.css">

Server Configuration

// Express.js
app.use(express.static('public', {
  maxAge: '1y',
  immutable: true,
  etag: true,
  lastModified: true,
}));

// Nginx
location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
  expires 1y;
  add_header Cache-Control "public, immutable";
}

Service Worker Caching

Service Worker Caching

Cache resources for offline use and faster loading.

Basic Service Worker

// sw.js
const CACHE_NAME = 'v1';
const ASSETS = [
  '/',
  '/index.html',
  '/styles.css',
  '/app.js',
  '/offline.html',
];

// Install event
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll(ASSETS);
    })
  );
});

// Fetch event
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request);
    })
  );
});

// Activate event
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames
          .filter((name) => name !== CACHE_NAME)
          .map((name) => caches.delete(name))
      );
    })
  );
});

Cache Strategies

// Cache First (for static assets)
async function cacheFirst(request) {
  const cached = await caches.match(request);
  if (cached) return cached;

  const response = await fetch(request);
  const cache = await caches.open(CACHE_NAME);
  cache.put(request, response.clone());
  return response;
}

// Network First (for API calls)
async function networkFirst(request) {
  try {
    const response = await fetch(request);
    const cache = await caches.open(CACHE_NAME);
    cache.put(request, response.clone());
    return response;
  } catch (error) {
    const cached = await caches.match(request);
    return cached || new Response('Offline', { status: 503 });
  }
}

// Stale While Revalidate
async function staleWhileRevalidate(request) {
  const cache = await caches.open(CACHE_NAME);
  const cached = await cache.match(request);

  const fetchPromise = fetch(request).then((response) => {
    cache.put(request, response.clone());
    return response;
  });

  return cached || fetchPromise;
}

Workbox

// Using Workbox
import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';

// Cache images
registerRoute(
  ({ request }) => request.destination === 'image',
  new CacheFirst({
    cacheName: 'images',
    plugins: [
      new CacheableResponsePlugin({
        statuses: [0, 200],
      }),
    ],
  })
);

// Cache API responses
registerRoute(
  ({ url }) => url.pathname.startsWith('/api/'),
  new NetworkFirst({
    cacheName: 'api',
    plugins: [
      new CacheableResponsePlugin({
        statuses: [0, 200],
      }),
    ],
  })
);

Cache Strategies

Cache Strategies

Choose the right caching strategy for different resources.

Strategy Matrix

Resource Strategy Rationale
HTML Network First Always get latest
CSS/JS Cache First + Revalidate Fast loading, occasional updates
Images Cache First Rarely change
API Data Stale While Revalidate Show cache, update in background
Fonts Cache First Very stable

Implementation

// sw.js with different strategies
self.addEventListener('fetch', (event) => {
  const { request } = event;
  const url = new URL(request.url);

  // HTML: Network First
  if (request.destination === 'document') {
    event.respondWith(networkFirst(request));
    return;
  }

  // Static Assets: Cache First
  if (request.destination === 'style' || request.destination === 'script') {
    event.respondWith(cacheFirst(request));
    return;
  }

  // Images: Cache First
  if (request.destination === 'image') {
    event.respondWith(cacheFirst(request));
    return;
  }

  // API: Stale While Revalidate
  if (url.pathname.startsWith('/api/')) {
    event.respondWith(staleWhileRevalidate(request));
    return;
  }

  // Default: Network First
  event.respondWith(networkFirst(request));
});

Cache Invalidation

// Versioned caches
const CACHE_VERSION = 'v2';
const CACHE_NAME = `cache-${CACHE_VERSION}`;

// On activate, delete old caches
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames
          .filter((name) => name.startsWith('cache-') && name !== CACHE_NAME)
          .map((name) => caches.delete(name))
      );
    })
  );
});

Offline Fallback

// Offline fallback page
const OFFLINE_URL = '/offline.html';

self.addEventListener('fetch', (event) => {
  if (event.request.mode === 'navigate') {
    event.respondWith(
      fetch(event.request).catch(() => {
        return caches.match(OFFLINE_URL);
      })
    );
  }
});

Best Practices

  1. Version your caches for easy invalidation
  2. Use different strategies for different resource types
  3. Implement offline fallback for navigation
  4. Clean up old caches in activate event
  5. Use Workbox for production service workers

Practice Problems

0/3solved
Build Browser Caching Component

Create a reusable React component implementing Browser Caching. Include proper state management and accessibility.

Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where needed
Browser Caching Testing

Write unit and integration tests for Browser Caching using React Testing Library.

Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility tests
Browser Caching Performance

Optimize Browser Caching for performance. Consider memoization, code splitting, and bundle size.

Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysis

Quiz

1. What does Cache-Control: immutable mean?

Question 1 options

2. When should you use network-first strategy?

Question 2 options

3. What is the primary purpose of Browser Caching?

Question 3 options

4. What is a common mistake when implementing Browser Caching?

Question 4 options

Flashcards

Question

What are the main HTTP caching headers?

Answer

Cache-Control, ETag, Last-Modified, and Vary control browser caching behavior.

Question

When should you use cache-first vs network-first?

Answer

Cache-first for static assets (images, CSS); network-first for dynamic content (HTML, API).

Question

What is a service worker?

Answer

A background script enabling caching, offline support, and background sync.

Question

Why version your caches?

Answer

To easily invalidate and delete old caches when updating service workers.

Question

What is Browser Caching?

Answer

Browser Caching is a key concept in frontend development.

Revision Notes

Key Takeaways

  • 1.Use content hashing for immutable assets
  • 2.Different resources need different caching strategies
  • 3.Service workers enable offline support and better caching
  • 4.Version caches for easy invalidation
  • 5.Use Workbox for production service workers

Interview Tips

  • Explain when to use cache-first vs network-first
  • Discuss service worker caching strategies
  • Know how to configure HTTP caching headers

Cheat Sheet

Browser Caching Cheat Sheet

HTTP Headers

  • Cache-Control: max-age, immutable, no-cache
  • ETag: Conditional requests
  • Vary: Different versions by header

Service Worker Strategies

  • Cache First: Static assets
  • Network First: HTML, API
  • Stale While Revalidate: Semi-dynamic

Workbox

registerRoute(
  ({ request }) => request.destination === 'image',
  new CacheFirst({ cacheName: 'images' })
);