Files of Caching strategies
wondelai/
Show the full text355 lines
Caching Strategies
The fastest network request is one that never happens. A well-designed caching strategy eliminates redundant data transfer, reduces server load, and dramatically improves load times for repeat visitors and subsequent navigations.
Table of Contents
- The Cache Hierarchy
- HTTP Cache-Control Headers
- Conditional Requests and Revalidation
- Content Hashing for Cache Busting
- Service Workers for Cache Control
- CDN Configuration
- Stale-While-Revalidate
- Caching Strategy by Resource Type
- Common Caching Mistakes
The Cache Hierarchy
Browsers check caches in a specific order before making a network request:
1. Memory cache (in-process, lost on tab close)
2. Service worker cache (programmable, persistent)
3. Disk cache (HTTP cache, persistent)
4. CDN / edge cache (network, shared across users)
5. Origin server (final fallback)
Each layer closer to the user is faster. Memory cache is near-instant. Disk cache avoids the network entirely. CDN cache reduces RTT by serving from a nearby edge location. The goal is to satisfy as many requests as possible from the closest cache layer.
HTTP Cache-Control Headers
The Cache-Control header is the primary mechanism for controlling browser and CDN caching behavior.
Essential directives
| Directive | Meaning | Use case |
|---|---|---|
max-age=N |
Cache for N seconds without revalidation | Static assets with known freshness |
no-cache |
Cache but always revalidate before use | HTML documents, API responses |
no-store |
Do not cache at all | Sensitive data (banking, health) |
immutable |
Never revalidate (even on reload) | Content-hashed static assets |
public |
Can be cached by shared caches (CDN) | Public content |
private |
Only browser can cache, not CDN | User-specific content |
stale-while-revalidate=N |
Serve stale for N seconds while fetching fresh | Near-real-time content |
stale-if-error=N |
Serve stale if origin returns an error | Fault tolerance |
Common caching patterns
Static assets with content hashing (optimal):
Cache-Control: max-age=31536000, immutable
Files like app.a1b2c3.js can be cached forever because the URL changes when content changes. immutable tells the browser to skip revalidation even when the user hits refresh.
HTML documents:
Cache-Control: no-cache
The browser caches the document but revalidates on every request. Combined with ETag, this enables 304 Not Modified responses that transfer only headers, not the full document.
API responses with near-real-time needs:
Cache-Control: max-age=0, stale-while-revalidate=60
Always revalidate, but if the origin is slow, serve the cached response and update in the background.
Sensitive content:
Cache-Control: no-store
Never cache. Use for authentication tokens, financial data, personal health information.
Shared public content:
Cache-Control: public, max-age=3600, stale-while-revalidate=86400
CDN can cache for 1 hour; serve stale for up to 24 hours while refreshing.
Conditional Requests and Revalidation
When a cached resource expires (or uses no-cache), the browser sends a conditional request to check if the resource has changed.
ETag (Entity Tag)
The server generates a unique identifier (hash) for the response content:
HTTP/1.1 200 OK
ETag: "abc123def456"
Cache-Control: no-cache
On revalidation, the browser sends:
GET /page.html HTTP/1.1
If-None-Match: "abc123def456"
If the content has not changed, the server responds:
HTTP/1.1 304 Not Modified
No body is transferred -- only headers. This saves bandwidth while ensuring freshness.
Last-Modified
A simpler mechanism using timestamps:
HTTP/1.1 200 OK
Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT
Revalidation:
GET /page.html HTTP/1.1
If-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT
ETag vs. Last-Modified: ETag is more precise (content-based), while Last-Modified has second-level granularity. Servers should support both; browsers prefer ETag when both are present.
Content Hashing for Cache Busting
The most effective caching pattern combines long-lived cache headers with content-hashed filenames:
styles.css → styles.a1b2c3.css
app.js → app.d4e5f6.js
logo.png → logo.g7h8i9.png
How it works:
- Build tools generate a hash of each file's contents
- The hash is embedded in the filename
- HTML references the hashed filename
- The server sets
Cache-Control: max-age=31536000, immutable - When the file changes, a new hash produces a new URL
- The browser treats it as a completely new resource
Implementation with common build tools:
Webpack:
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js',
}
Vite:
build: {
rollupOptions: {
output: {
entryFileNames: 'assets/[name].[hash].js',
chunkFileNames: 'assets/[name].[hash].js',
assetFileNames: 'assets/[name].[hash].[ext]',
}
}
}
The HTML document itself cannot be hashed (users navigate to /index.html, not /index.a1b2c3.html). This is why HTML uses no-cache while all referenced assets use content hashing.
Service Workers for Cache Control
Service workers provide a programmable cache layer between the browser and the network. They intercept every fetch request and can implement sophisticated caching strategies.
Cache-first (offline-first)
Serve from cache if available; fall back to network:
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(cached => cached || fetch(event.request))
);
});
Best for: Static assets (CSS, JS, images, fonts) that rarely change.
Network-first
Try the network; fall back to cache if offline:
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request)
.then(response => {
const clone = response.clone();
caches.open('dynamic').then(cache => cache.put(event.request, clone));
return response;
})
.catch(() => caches.match(event.request))
);
});
Best for: HTML documents and API responses where freshness matters.
Stale-while-revalidate
Serve from cache immediately; update cache in the background:
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(cached => {
const fetchPromise = fetch(event.request).then(response => {
const clone = response.clone();
caches.open('dynamic').then(cache => cache.put(event.request, clone));
return response;
});
return cached || fetchPromise;
})
);
});
Best for: Content that updates periodically but where instant display is preferred (news feeds, social timelines).
Precaching the app shell
During service worker installation, cache the core application shell:
const CACHE_NAME = 'app-shell-v1';
const SHELL_URLS = [
'/',
'/styles.css',
'/app.js',
'/offline.html',
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(SHELL_URLS))
);
});
Cache versioning and cleanup
Old caches must be cleaned up to prevent storage bloat:
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(
keys
.filter(key => key !== CACHE_NAME)
.map(key => caches.delete(key))
)
)
);
});
CDN Configuration
CDNs cache content at edge locations close to users, reducing RTT for cached responses.
CDN caching headers
The Cache-Control header controls both browser and CDN caching. Use s-maxage to set a different TTL for shared caches (CDNs) vs. browsers:
Cache-Control: public, max-age=60, s-maxage=3600
This tells browsers to cache for 60 seconds but CDNs to cache for 1 hour.
Vary header
The Vary header tells caches which request headers affect the response:
Vary: Accept-Encoding
Without Vary: Accept-Encoding, a CDN might serve a Brotli-compressed response to a client that only supports Gzip.
Common Vary values:
Accept-Encoding-- different compression (Brotli, Gzip, identity)Accept-- different content types (HTML vs. JSON, AVIF vs. WebP)Accept-Language-- different language versions
Warning: Vary: * or Vary: Cookie effectively disables CDN caching because every request differs.
Cache purging
When content changes, CDN caches must be invalidated:
- Purge by URL: Invalidate a specific resource
- Purge by tag: Tag resources with categories; purge all resources with a tag
- Purge by prefix: Invalidate all resources under a path
- Soft purge: Mark as stale; serve stale while fetching fresh (similar to
stale-while-revalidate)
Content-hashed URLs largely eliminate the need for cache purging of static assets. Focus purging on HTML and API responses.
Stale-While-Revalidate
The stale-while-revalidate directive is one of the most powerful caching tools:
Cache-Control: max-age=60, stale-while-revalidate=3600
Behavior:
- 0-60 seconds: Serve from cache without revalidation (fresh)
- 60-3660 seconds: Serve from cache immediately (stale) AND fetch a fresh copy in the background
- After 3660 seconds: Cache is completely stale; must wait for network
This pattern gives users instant responses while keeping content reasonably fresh. It is ideal for:
- API endpoints that update periodically
- Configuration data
- Product listings
- Any content where a few minutes of staleness is acceptable
Caching Strategy by Resource Type
| Resource | Cache-Control | Hash | Revalidation |
|---|---|---|---|
| HTML | no-cache |
No | ETag + 304 |
| CSS (bundled) | max-age=31536000, immutable |
Yes | None needed |
| JavaScript (bundled) | max-age=31536000, immutable |
Yes | None needed |
| Images (static) | max-age=31536000, immutable |
Yes | None needed |
| Fonts | max-age=31536000, immutable |
Yes | None needed |
| API responses | max-age=0, stale-while-revalidate=60 |
No | ETag + 304 |
| User-specific data | private, no-cache |
No | ETag + 304 |
| Sensitive data | no-store |
No | N/A |
Common Caching Mistakes
| Mistake | Consequence | Fix |
|---|---|---|
No Cache-Control on static assets |
Browser uses heuristic caching (unpredictable) | Explicitly set max-age and immutable |
no-store on everything |
Every visit fetches all resources from origin | Use no-cache for HTML; long cache + hash for assets |
Missing Vary: Accept-Encoding |
CDN serves wrong compression format | Add Vary: Accept-Encoding to compressed responses |
| Cache busting with query strings | Some CDNs ignore query strings; proxies may not cache | Use filename hashing instead of ?v=123 |
| No ETag on HTML | Conditional requests impossible; full document re-downloaded | Configure server to generate ETags |
max-age=0 without stale-while-revalidate |
Every request blocks on revalidation | Add stale-while-revalidate for better perceived performance |
| Service worker caching everything | Cache grows unbounded; stale content persists | Implement cache limits and versioned cleanup |
A comprehensive caching strategy is often the single highest-impact performance optimization -- it reduces server load, saves bandwidth, and makes repeat visits feel instant.
| 1 | # Caching Strategies |
| 2 | |
| 3 | The fastest network request is one that never happens. A well-designed caching strategy eliminates redundant data transfer, reduces server load, and dramatically improves load times for repeat visitors and subsequent navigations. |
| 4 | |
| 5 | |
| 6 | ## Table of Contents |
| 7 | [The Cache Hierarchy] |
| 8 | [HTTP Cache-Control Headers] |
| 9 | [Conditional Requests and Revalidation] |
| 10 | [Content Hashing for Cache Busting] |
| 11 | [Service Workers for Cache Control] |
| 12 | [CDN Configuration] |
| 13 | [Stale-While-Revalidate] |
| 14 | [Caching Strategy by Resource Type] |
| 15 | [Common Caching Mistakes] |
| 16 | |
| 17 | |
| 18 | |
| 19 | ## The Cache Hierarchy |
| 20 | |
| 21 | Browsers check caches in a specific order before making a network request: |
| 22 | |
| 23 | |
| 24 | 1. Memory cache (in-process, lost on tab close) |
| 25 | 2. Service worker cache (programmable, persistent) |
| 26 | 3. Disk cache (HTTP cache, persistent) |
| 27 | 4. CDN / edge cache (network, shared across users) |
| 28 | 5. Origin server (final fallback) |
| 29 | |
| 30 | |
| 31 | Each layer closer to the user is faster. Memory cache is near-instant. Disk cache avoids the network entirely. CDN cache reduces RTT by serving from a nearby edge location. The goal is to satisfy as many requests as possible from the closest cache layer. |
| 32 | |
| 33 | ## HTTP Cache-Control Headers |
| 34 | |
| 35 | The `Cache-Control` header is the primary mechanism for controlling browser and CDN caching behavior. |
| 36 | |
| 37 | ### Essential directives |
| 38 | |
| 39 | | Directive | Meaning | Use case | |
| 40 | |-----------|---------|----------| |
| 41 | | `max-age=N` | Cache for N seconds without revalidation | Static assets with known freshness | |
| 42 | | `no-cache` | Cache but always revalidate before use | HTML documents, API responses | |
| 43 | | `no-store` | Do not cache at all | Sensitive data (banking, health) | |
| 44 | | `immutable` | Never revalidate (even on reload) | Content-hashed static assets | |
| 45 | | `public` | Can be cached by shared caches (CDN) | Public content | |
| 46 | | `private` | Only browser can cache, not CDN | User-specific content | |
| 47 | | `stale-while-revalidate=N` | Serve stale for N seconds while fetching fresh | Near-real-time content | |
| 48 | | `stale-if-error=N` | Serve stale if origin returns an error | Fault tolerance | |
| 49 | |
| 50 | ### Common caching patterns |
| 51 | |
| 52 | **Static assets with content hashing (optimal):** |
| 53 | |
| 54 | Cache-Control: max-age=31536000, immutable |
| 55 | |
| 56 | Files like `app.a1b2c3.js` can be cached forever because the URL changes when content changes. `immutable` tells the browser to skip revalidation even when the user hits refresh. |
| 57 | |
| 58 | **HTML documents:** |
| 59 | |
| 60 | Cache-Control: no-cache |
| 61 | |
| 62 | The browser caches the document but revalidates on every request. Combined with `ETag`, this enables `304 Not Modified` responses that transfer only headers, not the full document. |
| 63 | |
| 64 | **API responses with near-real-time needs:** |
| 65 | |
| 66 | Cache-Control: max-age=0, stale-while-revalidate=60 |
| 67 | |
| 68 | Always revalidate, but if the origin is slow, serve the cached response and update in the background. |
| 69 | |
| 70 | **Sensitive content:** |
| 71 | |
| 72 | Cache-Control: no-store |
| 73 | |
| 74 | Never cache. Use for authentication tokens, financial data, personal health information. |
| 75 | |
| 76 | **Shared public content:** |
| 77 | |
| 78 | Cache-Control: public, max-age=3600, stale-while-revalidate=86400 |
| 79 | |
| 80 | CDN can cache for 1 hour; serve stale for up to 24 hours while refreshing. |
| 81 | |
| 82 | ## Conditional Requests and Revalidation |
| 83 | |
| 84 | When a cached resource expires (or uses `no-cache`), the browser sends a conditional request to check if the resource has changed. |
| 85 | |
| 86 | ### ETag (Entity Tag) |
| 87 | |
| 88 | The server generates a unique identifier (hash) for the response content: |
| 89 | |
| 90 | |
| 91 | HTTP/1.1 200 OK |
| 92 | ETag: "abc123def456" |
| 93 | Cache-Control: no-cache |
| 94 | |
| 95 | |
| 96 | On revalidation, the browser sends: |
| 97 | |
| 98 | GET /page.html HTTP/1.1 |
| 99 | If-None-Match: "abc123def456" |
| 100 | |
| 101 | |
| 102 | If the content has not changed, the server responds: |
| 103 | |
| 104 | HTTP/1.1 304 Not Modified |
| 105 | |
| 106 | |
| 107 | No body is transferred -- only headers. This saves bandwidth while ensuring freshness. |
| 108 | |
| 109 | ### Last-Modified |
| 110 | |
| 111 | A simpler mechanism using timestamps: |
| 112 | |
| 113 | |
| 114 | HTTP/1.1 200 OK |
| 115 | Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT |
| 116 | |
| 117 | |
| 118 | Revalidation: |
| 119 | |
| 120 | GET /page.html HTTP/1.1 |
| 121 | If-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT |
| 122 | |
| 123 | |
| 124 | **ETag vs. Last-Modified:** ETag is more precise (content-based), while Last-Modified has second-level granularity. Servers should support both; browsers prefer ETag when both are present. |
| 125 | |
| 126 | ## Content Hashing for Cache Busting |
| 127 | |
| 128 | The most effective caching pattern combines long-lived cache headers with content-hashed filenames: |
| 129 | |
| 130 | |
| 131 | styles.css → styles.a1b2c3.css |
| 132 | app.js → app.d4e5f6.js |
| 133 | logo.png → logo.g7h8i9.png |
| 134 | |
| 135 | |
| 136 | **How it works:** |
| 137 | Build tools generate a hash of each file's contents |
| 138 | The hash is embedded in the filename |
| 139 | HTML references the hashed filename |
| 140 | The server sets `Cache-Control: max-age=31536000, immutable` |
| 141 | When the file changes, a new hash produces a new URL |
| 142 | The browser treats it as a completely new resource |
| 143 | |
| 144 | **Implementation with common build tools:** |
| 145 | |
| 146 | Webpack: |
| 147 | |
| 148 | output: { |
| 149 | filename: '[name].[contenthash].js', |
| 150 | chunkFilename: '[name].[contenthash].js', |
| 151 | } |
| 152 | |
| 153 | |
| 154 | Vite: |
| 155 | |
| 156 | build: { |
| 157 | rollupOptions: { |
| 158 | output: { |
| 159 | entryFileNames: 'assets/[name].[hash].js', |
| 160 | chunkFileNames: 'assets/[name].[hash].js', |
| 161 | assetFileNames: 'assets/[name].[hash].[ext]', |
| 162 | } |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | |
| 167 | **The HTML document itself cannot be hashed** (users navigate to `/index.html`, not `/index.a1b2c3.html`). This is why HTML uses `no-cache` while all referenced assets use content hashing. |
| 168 | |
| 169 | ## Service Workers for Cache Control |
| 170 | |
| 171 | Service workers provide a programmable cache layer between the browser and the network. They intercept every fetch request and can implement sophisticated caching strategies. |
| 172 | |
| 173 | ### Cache-first (offline-first) |
| 174 | |
| 175 | Serve from cache if available; fall back to network: |
| 176 | |
| 177 | |
| 178 | self.addEventListener('fetch', event => { |
| 179 | event.respondWith( |
| 180 | caches.match(event.request) |
| 181 | .then(cached => cached || fetch(event.request)) |
| 182 | ); |
| 183 | }); |
| 184 | |
| 185 | |
| 186 | **Best for:** Static assets (CSS, JS, images, fonts) that rarely change. |
| 187 | |
| 188 | ### Network-first |
| 189 | |
| 190 | Try the network; fall back to cache if offline: |
| 191 | |
| 192 | |
| 193 | self.addEventListener('fetch', event => { |
| 194 | event.respondWith( |
| 195 | fetch(event.request) |
| 196 | .then(response => { |
| 197 | const clone = response.clone(); |
| 198 | caches.open('dynamic').then(cache => cache.put(event.request, clone)); |
| 199 | return response; |
| 200 | }) |
| 201 | .catch(() => caches.match(event.request)) |
| 202 | ); |
| 203 | }); |
| 204 | |
| 205 | |
| 206 | **Best for:** HTML documents and API responses where freshness matters. |
| 207 | |
| 208 | ### Stale-while-revalidate |
| 209 | |
| 210 | Serve from cache immediately; update cache in the background: |
| 211 | |
| 212 | |
| 213 | self.addEventListener('fetch', event => { |
| 214 | event.respondWith( |
| 215 | caches.match(event.request).then(cached => { |
| 216 | const fetchPromise = fetch(event.request).then(response => { |
| 217 | const clone = response.clone(); |
| 218 | caches.open('dynamic').then(cache => cache.put(event.request, clone)); |
| 219 | return response; |
| 220 | }); |
| 221 | return cached || fetchPromise; |
| 222 | }) |
| 223 | ); |
| 224 | }); |
| 225 | |
| 226 | |
| 227 | **Best for:** Content that updates periodically but where instant display is preferred (news feeds, social timelines). |
| 228 | |
| 229 | ### Precaching the app shell |
| 230 | |
| 231 | During service worker installation, cache the core application shell: |
| 232 | |
| 233 | |
| 234 | const CACHE_NAME = 'app-shell-v1'; |
| 235 | const SHELL_URLS = [ |
| 236 | '/', |
| 237 | '/styles.css', |
| 238 | '/app.js', |
| 239 | '/offline.html', |
| 240 | ]; |
| 241 | |
| 242 | self.addEventListener('install', event => { |
| 243 | event.waitUntil( |
| 244 | caches.open(CACHE_NAME) |
| 245 | .then(cache => cache.addAll(SHELL_URLS)) |
| 246 | ); |
| 247 | }); |
| 248 | |
| 249 | |
| 250 | ### Cache versioning and cleanup |
| 251 | |
| 252 | Old caches must be cleaned up to prevent storage bloat: |
| 253 | |
| 254 | |
| 255 | self.addEventListener('activate', event => { |
| 256 | event.waitUntil( |
| 257 | caches.keys().then(keys => |
| 258 | Promise.all( |
| 259 | keys |
| 260 | .filter(key => key !== CACHE_NAME) |
| 261 | .map(key => caches.delete(key)) |
| 262 | ) |
| 263 | ) |
| 264 | ); |
| 265 | }); |
| 266 | |
| 267 | |
| 268 | ## CDN Configuration |
| 269 | |
| 270 | CDNs cache content at edge locations close to users, reducing RTT for cached responses. |
| 271 | |
| 272 | ### CDN caching headers |
| 273 | |
| 274 | The `Cache-Control` header controls both browser and CDN caching. Use `s-maxage` to set a different TTL for shared caches (CDNs) vs. browsers: |
| 275 | |
| 276 | |
| 277 | Cache-Control: public, max-age=60, s-maxage=3600 |
| 278 | |
| 279 | |
| 280 | This tells browsers to cache for 60 seconds but CDNs to cache for 1 hour. |
| 281 | |
| 282 | ### Vary header |
| 283 | |
| 284 | The `Vary` header tells caches which request headers affect the response: |
| 285 | |
| 286 | |
| 287 | Vary: Accept-Encoding |
| 288 | |
| 289 | |
| 290 | Without `Vary: Accept-Encoding`, a CDN might serve a Brotli-compressed response to a client that only supports Gzip. |
| 291 | |
| 292 | Common `Vary` values: |
| 293 | `Accept-Encoding` -- different compression (Brotli, Gzip, identity) |
| 294 | `Accept` -- different content types (HTML vs. JSON, AVIF vs. WebP) |
| 295 | `Accept-Language` -- different language versions |
| 296 | |
| 297 | **Warning:** `Vary: *` or `Vary: Cookie` effectively disables CDN caching because every request differs. |
| 298 | |
| 299 | ### Cache purging |
| 300 | |
| 301 | When content changes, CDN caches must be invalidated: |
| 302 | |
| 303 | **Purge by URL:** Invalidate a specific resource |
| 304 | **Purge by tag:** Tag resources with categories; purge all resources with a tag |
| 305 | **Purge by prefix:** Invalidate all resources under a path |
| 306 | **Soft purge:** Mark as stale; serve stale while fetching fresh (similar to `stale-while-revalidate`) |
| 307 | |
| 308 | Content-hashed URLs largely eliminate the need for cache purging of static assets. Focus purging on HTML and API responses. |
| 309 | |
| 310 | ## Stale-While-Revalidate |
| 311 | |
| 312 | The `stale-while-revalidate` directive is one of the most powerful caching tools: |
| 313 | |
| 314 | |
| 315 | Cache-Control: max-age=60, stale-while-revalidate=3600 |
| 316 | |
| 317 | |
| 318 | Behavior: |
| 319 | **0-60 seconds:** Serve from cache without revalidation (fresh) |
| 320 | **60-3660 seconds:** Serve from cache immediately (stale) AND fetch a fresh copy in the background |
| 321 | **After 3660 seconds:** Cache is completely stale; must wait for network |
| 322 | |
| 323 | This pattern gives users instant responses while keeping content reasonably fresh. It is ideal for: |
| 324 | API endpoints that update periodically |
| 325 | Configuration data |
| 326 | Product listings |
| 327 | Any content where a few minutes of staleness is acceptable |
| 328 | |
| 329 | ## Caching Strategy by Resource Type |
| 330 | |
| 331 | | Resource | Cache-Control | Hash | Revalidation | |
| 332 | |----------|--------------|------|-------------| |
| 333 | | HTML | `no-cache` | No | ETag + 304 | |
| 334 | | CSS (bundled) | `max-age=31536000, immutable` | Yes | None needed | |
| 335 | | JavaScript (bundled) | `max-age=31536000, immutable` | Yes | None needed | |
| 336 | | Images (static) | `max-age=31536000, immutable` | Yes | None needed | |
| 337 | | Fonts | `max-age=31536000, immutable` | Yes | None needed | |
| 338 | | API responses | `max-age=0, stale-while-revalidate=60` | No | ETag + 304 | |
| 339 | | User-specific data | `private, no-cache` | No | ETag + 304 | |
| 340 | | Sensitive data | `no-store` | No | N/A | |
| 341 | |
| 342 | ## Common Caching Mistakes |
| 343 | |
| 344 | | Mistake | Consequence | Fix | |
| 345 | |---------|------------|-----| |
| 346 | | No `Cache-Control` on static assets | Browser uses heuristic caching (unpredictable) | Explicitly set `max-age` and `immutable` | |
| 347 | | `no-store` on everything | Every visit fetches all resources from origin | Use `no-cache` for HTML; long cache + hash for assets | |
| 348 | | Missing `Vary: Accept-Encoding` | CDN serves wrong compression format | Add `Vary: Accept-Encoding` to compressed responses | |
| 349 | | Cache busting with query strings | Some CDNs ignore query strings; proxies may not cache | Use filename hashing instead of `?v=123` | |
| 350 | | No ETag on HTML | Conditional requests impossible; full document re-downloaded | Configure server to generate ETags | |
| 351 | | `max-age=0` without `stale-while-revalidate` | Every request blocks on revalidation | Add `stale-while-revalidate` for better perceived performance | |
| 352 | | Service worker caching everything | Cache grows unbounded; stale content persists | Implement cache limits and versioned cleanup | |
| 353 | |
| 354 | A comprehensive caching strategy is often the single highest-impact performance optimization -- it reduces server load, saves bandwidth, and makes repeat visits feel instant. |
| 355 |
Discussion
Alternatives
Browse more free Claude skills or everything in Development.