Skip to main content

[Medium] πŸ“„ HTTP Caching

1. What is HTTP caching and why is it important?​

What is HTTP caching? Why is it important?

HTTP caching is a technique that stores HTTP responses temporarily in clients (browsers) or intermediate servers, so later requests can reuse cached data instead of hitting the origin again.

Cache vs Temporary Storage​

In Chinese technical context, these two terms are often mixed, but they represent different purposes.

Cache​

Definition: a stored copy used for performance optimization, focusing on reuse and faster access.

Characteristics:

  • βœ… Goal: performance improvement
  • βœ… Data is expected to be reused
  • βœ… Explicit expiration/revalidation strategy
  • βœ… Usually a copy of original data

Examples:

// HTTP Cache - cache API responses
Cache-Control: max-age=3600 // cache 1 hour

// Memory Cache - cache computation result
const cache = new Map();
function fibonacci(n) {
if (cache.has(n)) return cache.get(n); // reuse cache
const result = /* compute */;
cache.set(n, result);
return result;
}

Temporary Storage​

Definition: data stored for temporary workflow needs, emphasizing short-lived lifecycle.

Characteristics:

  • βœ… Goal: temporary retention
  • βœ… Reuse is optional
  • βœ… Usually shorter lifecycle
  • βœ… May hold intermediate state

Examples:

// sessionStorage - temporary form data
sessionStorage.setItem('formData', JSON.stringify(form));

// temporary upload file path
const tempFile = await uploadToTemp(file);
await processFile(tempFile);
await deleteTempFile(tempFile);

Comparison​

FeatureCacheTemporary Storage
Primary goalPerformanceTemporary retention
ReuseYes, often repeatedNot guaranteed
LifecyclePolicy-drivenUsually short
Typical useHTTP cache, memory cachesessionStorage, temp files
English termCacheTemp / Temporary / Buffer

Practical distinction​

// ===== Cache scenarios =====

// 1) HTTP cache: reuse API response
fetch('/api/users').then((response) => response.json());
fetch('/api/users').then((response) => response.json());

// 2) memoization cache
const memoize = (fn) => {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
};

// ===== Temporary storage scenarios =====

// 1) save draft on unload
window.addEventListener('beforeunload', () => {
sessionStorage.setItem('formDraft', JSON.stringify(formData));
});

// 2) temporary upload processing
async function handleUpload(file) {
const tempPath = await uploadToTempStorage(file);
const processed = await processFile(tempPath);
await deleteTempFile(tempPath);
return processed;
}

// 3) temporary intermediate results
const tempResults = [];
for (const item of items) {
tempResults.push(process(item));
}
const final = combine(tempResults);

In web development​

// HTTP Cache (long-lived, reusable)
Cache-Control: public, max-age=31536000, immutable

// sessionStorage (temporary, tab-scoped)
sessionStorage.setItem('tempData', data)

// localStorage (persistent storage, not primarily a cache optimization layer)
localStorage.setItem('userPreferences', prefs)

Why this distinction matters​

  1. Design decisions:
    • Need performance optimization -> cache
    • Need temporary persistence -> temp storage
  2. Resource management:
    • Cache focuses on hit rate and expiration strategy
    • Temp storage focuses on cleanup timing and size limits
  3. Interview clarity:
    • Performance question -> discuss caching strategy
    • Temporary data question -> discuss temp storage strategy

This article mainly focuses on cache, especially HTTP caching.

Benefits of caching​

  1. Fewer network requests
  2. Lower server load
  3. Faster page loads
  4. Lower bandwidth usage
  5. Better user experience

Browser cache layers​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Browser Cache Layers β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 1. Memory Cache β”‚
β”‚ - Fastest, small capacity β”‚
β”‚ - Cleared with tab/session end β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 2. Disk Cache β”‚
β”‚ - Slower, larger capacity β”‚
β”‚ - Persistent storage β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 3. Service Worker Cache β”‚
β”‚ - Fully controlled by app β”‚
β”‚ - Enables offline behavior β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2. What are the HTTP caching strategies?​

What caching strategies exist in HTTP?

Strategy categories​

HTTP Caching Strategies
β”œβ”€β”€ Strong Cache (Fresh)
β”‚ β”œβ”€β”€ Cache-Control
β”‚ └── Expires
└── Validation Cache (Negotiation)
β”œβ”€β”€ Last-Modified / If-Modified-Since
└── ETag / If-None-Match

1. Strong cache (fresh cache)​

Behavior: browser serves from local cache directly without sending request to origin.

Cache-Control (HTTP/1.1)​

Cache-Control: max-age=3600

Common directives:

// 1) max-age: freshness lifetime in seconds
Cache-Control: max-age=3600

// 2) no-cache: allow caching but require revalidation before reuse
Cache-Control: no-cache

// 3) no-store: do not cache at all
Cache-Control: no-store

// 4) public: cacheable by browser/CDN/proxy
Cache-Control: public, max-age=31536000

// 5) private: browser cache only
Cache-Control: private, max-age=3600

// 6) immutable: content will not change during freshness lifetime
Cache-Control: public, max-age=31536000, immutable

// 7) must-revalidate: once stale, must revalidate
Cache-Control: max-age=3600, must-revalidate

Expires (HTTP/1.0, legacy)​

Expires: Wed, 21 Oct 2025 07:28:00 GMT

Issues:

  • Uses absolute time
  • Depends on client clock correctness
  • Mostly replaced by Cache-Control

2. Validation cache (negotiation)​

Behavior: browser asks server whether resource changed.

Last-Modified / If-Modified-Since​

# first response
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT

# subsequent request
If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT

Flow:

  1. First request: server sends Last-Modified
  2. Next request: browser sends If-Modified-Since
  3. Unchanged: server returns 304 Not Modified
  4. Changed: server returns 200 OK + new body

ETag / If-None-Match​

# first response
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

# subsequent request
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"

Advantages:

  • More precise than Last-Modified
  • Content-driven (hash or version token)
  • Can detect changes not visible in second-level timestamps

Last-Modified vs ETag​

FeatureLast-ModifiedETag
PrecisionSecond-levelContent token/hash, more precise
CostLowerMay require extra computation
Good forGeneral static filesExact validation control
PriorityLowerHigher (ETag preferred when both exist)

3. How does browser caching work?​

What is the browser cache workflow?

Full workflow​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Browser Resource Request Flow β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
↓
1. Check Memory Cache
↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Hit cache? β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Yes β”‚ No
↓
2. Check Disk Cache
↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Hit cache? β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Yes β”‚ No
↓
3. Check Service Worker
↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Hit cache? β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Yes β”‚ No
↓
4. Check freshness
↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Is stale? β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Yes β”‚ No
↓
5. Revalidate with server
↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Modified? β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Yes β”‚ No (304)
↓
6. Request new content
↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Return 200 β”‚
β”‚ with new body β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Practical example​

// first request
GET /api/data.json
Response:
200 OK
Cache-Control: max-age=3600
ETag: "abc123"

{ data: "..." }

// within 1 hour
// strong cache hit -> local use, no request
// status shown by devtools: from disk cache or from memory cache

// after 1 hour
GET /api/data.json
If-None-Match: "abc123"

// unchanged
Response:
304 Not Modified

// changed
Response:
200 OK
ETag: "def456"

{ data: "new data" }

4. What are common caching strategies?​

Common practical caching strategies

1. Long-lived static assets​

// HTML: no long cache, always validate
Cache-Control: no-cache

// CSS/JS with hash: long immutable cache
Cache-Control: public, max-age=31536000, immutable
// filename: main.abc123.js

Principle:

  • HTML should stay fresh to reference latest asset hashes
  • Hashed static assets can be cached for a long time
  • Content change -> filename change -> new download

2. Frequently updated resources​

// API data: short cache + revalidation
Cache-Control: max-age=60, must-revalidate
ETag: "abc123"

3. Image strategies​

// user avatars: medium cache
Cache-Control: public, max-age=86400

// logo/icons: longer cache
Cache-Control: public, max-age=2592000

// dynamic images: validation
Cache-Control: no-cache
ETag: "image-hash"

4. Suggested policies by resource type​

const cachingStrategies = {
html: 'Cache-Control: no-cache',
staticWithHash: 'Cache-Control: public, max-age=31536000, immutable',
staticAssets: 'Cache-Control: public, max-age=2592000',
apiData: 'Cache-Control: private, max-age=60',
userData: 'Cache-Control: private, no-cache',
sensitive: 'Cache-Control: no-store',
};

5. Service Worker caching​

Service Worker caching

Service Worker gives full control over runtime caching and offline behavior.

Basic usage​

// register Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
// sw.js
const CACHE_NAME = 'my-app-v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/scripts/main.js',
'/images/logo.png',
];

// install: precache static assets
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(urlsToCache);
})
);
});

// fetch: cache-first example
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});

// activate: cleanup old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
})
);
});

Common SW strategies​

1. Cache First​

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

2. Network First​

// best for API requests
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request)
.then((response) => {
const responseClone = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseClone);
});
return response;
})
.catch(() => {
return caches.match(event.request);
})
);
});

3. Stale While Revalidate​

// best for fast response + background update
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
const fetchPromise = fetch(event.request).then((networkResponse) => {
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, networkResponse.clone());
});
return networkResponse;
});

return cachedResponse || fetchPromise;
})
);
});

6. How to implement cache busting?​

How to implement cache busting?

Cache busting ensures users fetch the latest assets when content changes.

// with Webpack/Vite
// output: main.abc123.js

// webpack.config.js
module.exports = {
output: {
filename: '[name].[contenthash].js',
},
};
<script src="/js/main.abc123.js"></script>

Pros:

  • βœ… New filename forces download
  • βœ… Old files remain cacheable
  • βœ… Industry best practice

Method 2: query version​

<script src="/js/main.js?v=1.2.3"></script>
<link rel="stylesheet" href="/css/style.css?v=1.2.3" />

Cons:

  • ❌ Some CDNs/proxies treat query-string caching differently
  • ❌ Manual version maintenance

Method 3: timestamp​

// common in development only
const timestamp = Date.now();
const script = document.createElement('script');
script.src = `/js/main.js?t=${timestamp}`;
document.body.appendChild(script);

Use case:

  • development cache bypass
  • not ideal for production

7. Common caching interview questions​

Common caching interview questions

Question 1: How to prevent HTML from being cached?​

Click to view answer
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0

or HTML meta tags:

<meta
http-equiv="Cache-Control"
content="no-cache, no-store, must-revalidate"
/>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />

Question 2: Why use ETag instead of only Last-Modified?​

Click to view answer

Advantages of ETag:

  1. More precise
  2. Content-based validation
  3. Avoids timestamp edge cases (re-deploy with same content)
  4. Better in distributed systems with unsynced clocks

Example:

// content unchanged, deployment time changed
// Last-Modified changes, but content is identical

ETag: 'hash-of-abc'; // stable if content unchanged

Question 3: difference between from disk cache and from memory cache?​

Click to view answer
FeatureMemory CacheDisk Cache
StorageRAMDisk
SpeedVery fastSlower
CapacitySmallerLarger
PersistenceUsually short-livedPersistent
PriorityHigherLower

Typical loading order (conceptual):

1. Memory Cache
2. Service Worker Cache
3. Disk Cache
4. Revalidation / Network

Question 4: how to force browser reload resources?​

Click to view answer

Development:

// Hard Reload
// Disable cache in DevTools

const script = document.createElement('script');
script.src = `/js/main.js?t=${Date.now()}`;

Production:

// hashed filenames (best)
main.abc123.js

// version query
<script src="/js/main.js?v=2.0.0"></script>

// cache policy
Cache-Control: no-cache
Cache-Control: no-store

Question 5: how to implement offline cache in PWA?​

Click to view answer
// sw.js
const CACHE_NAME = 'pwa-v1';
const OFFLINE_URL = '/offline.html';

self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll([
OFFLINE_URL,
'/styles/offline.css',
'/images/offline-icon.png',
]);
})
);
});

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

Full PWA strategy often combines:

// 1) static assets: cache-first
// 2) API: network-first
// 3) images: cache-first
// 4) HTML navigation: network-first + offline fallback

8. Best practices​

Best practices

// 1. HTML: no long cache to ensure latest entry document
Cache-Control: no-cache

// 2. CSS/JS with hash: long immutable cache
// filename example: main.abc123.js
Cache-Control: public, max-age=31536000, immutable

// 3. Images: medium/long cache
Cache-Control: public, max-age=2592000

// 4. API data: short cache + validation
Cache-Control: private, max-age=60
ETag: "api-response-hash"

// 5. Service Worker for offline support

❌ Avoid​

// bad: long cache for HTML entry document
Cache-Control: max-age=31536000

// bad: relying only on Expires
Expires: Wed, 21 Oct 2025 07:28:00 GMT

// bad: no explicit cache headers

// bad: same policy for all resource types
Cache-Control: max-age=3600

Cache strategy decision tree​

Is it static asset?
β”œβ”€ Yes -> filename has hash?
β”‚ β”œβ”€ Yes -> long immutable cache (max-age=31536000, immutable)
β”‚ └─ No -> medium/long cache (e.g. max-age=2592000)
└─ No -> Is it HTML?
β”œβ”€ Yes -> no-cache
└─ No -> Is it API?
β”œβ”€ Yes -> short cache + validation (max-age=60 + ETag)
└─ No -> decide by update frequency

Reference​