Skip to content

Commit a1fe81e

Browse files
committed
feat: Update cache key generation to include a whitelist of headers affecting response content
1 parent 3f78756 commit a1fe81e

File tree

4 files changed

+90
-5
lines changed

4 files changed

+90
-5
lines changed

README.md

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,14 +1336,52 @@ By default, `fetchff` generates a cache key automatically using a combination of
13361336
| ----------------- | ----------------------------------------------------------------------------------------- | --------------- |
13371337
| `method` | The HTTP method used for the request (e.g., GET, POST). | `'GET'` |
13381338
| `url` | The full request URL, including the base URL and endpoint path. | `''` |
1339-
| `headers` | Request headers, included as a stringified object. | |
1339+
| `headers` | Request headers, **filtered to include only cache-relevant headers** (see below). | |
13401340
| `body` | The request payload (for POST, PUT, PATCH, etc.), stringified if it's an object or array. | |
13411341
| `credentials` | Indicates whether credentials (cookies) are included in the request. | `'same-origin'` |
13421342
| `params` | Query parameters serialized into the URL (objects, arrays, etc. are stringified). | |
13431343
| `urlPathParams` | Dynamic URL path parameters (e.g., `/user/:id`), stringified and encoded. | |
13441344
| `withCredentials` | Whether credentials (cookies) are included in the request. | |
13451345

1346-
These properties are combined and hashed to create a unique cache key for each request. This ensures that requests with different parameters, bodies, or headers are cached separately.
1346+
#### Header Filtering for Cache Keys
1347+
1348+
To ensure stable cache keys and prevent unnecessary cache misses, `fetchff` only includes headers that affect response content in cache key generation. The following headers are included:
1349+
1350+
**Content Negotiation:**
1351+
1352+
- `accept` - Affects response format (JSON, HTML, etc.)
1353+
- `accept-language` - Affects localization of response
1354+
- `accept-encoding` - Affects response compression
1355+
1356+
**Authentication & Authorization:**
1357+
1358+
- `authorization` - Affects access to protected resources
1359+
- `x-api-key` - Token-based access control
1360+
- `cookie` - Session-based authentication
1361+
1362+
**Request Context:**
1363+
1364+
- `content-type` - Affects how request body is interpreted
1365+
- `origin` - Relevant for CORS or tenant-specific APIs
1366+
- `referer` - May influence API behavior
1367+
- `user-agent` - Only if server returns client-specific content
1368+
1369+
**Custom Headers:**
1370+
1371+
- `x-requested-with` - Distinguishes AJAX requests
1372+
- `x-client-id` - Per-client/partner identity
1373+
- `x-tenant-id` - Multi-tenant segmentation
1374+
- `x-user-id` - Explicit user context
1375+
- `x-app-version` - Version-specific behavior
1376+
- `x-feature-flag` - Feature rollout controls
1377+
- `x-device-id` - Device-specific responses
1378+
- `x-platform` - Platform-specific content (iOS, Android, web)
1379+
- `x-session-id` - Session-specific responses
1380+
- `x-locale` - Locale-specific content
1381+
1382+
Headers like `user-agent`, `accept-encoding`, `connection`, `cache-control`, tracking IDs, and proxy-related headers are **excluded** from cache key generation as they don't affect the actual response content.
1383+
1384+
These properties are combined and hashed to create a unique cache key for each request. This ensures that requests with different parameters, bodies, or cache-relevant headers are cached separately while maintaining stable cache keys across requests that only differ in non-essential headers. If that does not suffice, you can always use `cacheKey` (string | function) and supply it to particular requests. You can also build your own `cacheKey` function and simply update defaults to reflect it in all requests. Auto key generation would be entirely skipped in such scenarios.
13471385

13481386
</details>
13491387

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
},
7474
{
7575
"path": "dist/browser/index.global.js",
76-
"limit": "5.5 KB"
76+
"limit": "5.6 KB"
7777
},
7878
{
7979
"path": "dist/node/index.js",

src/cache-manager.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,49 @@ const DELIMITER = '|';
2525
const MIN_LENGTH_TO_HASH = 64;
2626
const CACHE_KEY_SANITIZE_PATTERN = new RegExp('[^\\w\\-_|]', 'g');
2727

28+
/**
29+
* Headers that may affect HTTP response content and should be included in cache key generation.
30+
* All header names must be lowercase to match normalized request headers.
31+
*/
32+
const CACHE_KEY_HEADER_WHITELIST = new Set([
33+
// Content negotiation
34+
'accept', // Affects response format (e.g. JSON, HTML)
35+
'accept-language', // Affects localization of the response
36+
'accept-encoding', // Affects response compression (e.g. gzip, br)
37+
38+
// Authentication
39+
'authorization', // Affects access to protected resources
40+
41+
// Request body metadata
42+
'content-type', // Affects how the request body is interpreted
43+
44+
// Request context hints
45+
'x-requested-with', // Commonly used to distinguish AJAX requests
46+
47+
// Optional headers
48+
'referer', // May influence behavior in some APIs
49+
'origin', // Relevant in CORS or tenant-specific APIs
50+
'user-agent', // Included only for reason if server returns client-specific content
51+
52+
// Cookies — only if server uses session-based responses
53+
'cookie', // Can fragment cache heavily; use only if necessary
54+
55+
// Custom headers that may affect response content
56+
'x-api-key', // Token-based access, often affects authorization
57+
'x-requested-with', // AJAX requests (used historically for distinguishing frontend calls)
58+
'x-client-id', // Per-client/partner identity; often used in multi-tenant APIs
59+
'x-tenant-id', // Multi-tenant segmentation; often changes response per tenant
60+
'x-user-id', // Explicit user context (less common, but may exist)
61+
62+
'x-app-version', // Used for version-specific behavior (e.g. mobile apps)
63+
'x-feature-flag', // Controls feature rollout behavior server-side
64+
'x-device-id', // Used when response varies per device/app instance
65+
'x-platform', // e.g. 'ios', 'android', 'web' — used in apps that serve different content
66+
67+
'x-session-id', // Only if backend uses it to affect the response directly (rare)
68+
'x-locale', // Sometimes used in addition to or instead of `accept-language`
69+
]);
70+
2871
/**
2972
* Generates a unique cache key for a given URL and fetch options, ensuring that key factors
3073
* like method, headers, body, and other options are included in the cache key.
@@ -83,6 +126,8 @@ export function generateCacheKey(
83126
obj = headers as Record<string, string>;
84127
}
85128

129+
// Filter headers to only include those that affect request identity
130+
// Include only headers that affect request identity, not execution behavior
86131
const keys = Object.keys(obj);
87132
const len = keys.length;
88133

@@ -93,7 +138,9 @@ export function generateCacheKey(
93138

94139
let str = '';
95140
for (let i = 0; i < len; ++i) {
96-
str += keys[i] + ':' + obj[keys[i]] + ';';
141+
if (CACHE_KEY_HEADER_WHITELIST.has(keys[i].toLowerCase())) {
142+
str += keys[i] + ':' + obj[keys[i]] + ';';
143+
}
97144
}
98145

99146
if (str.length > MIN_LENGTH_TO_HASH) {

test/cache-manager.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ describe('Cache Manager', () => {
8585
} as never);
8686

8787
expect(key).toContain(
88-
'GET|httpsapiexamplecomdata|same-origin|1530632817',
88+
'GET|httpsapiexamplecomdata|same-origin|accept-encodinggzipdeflatebrcontent-typeapplicationjson',
8989
);
9090
});
9191

0 commit comments

Comments
 (0)