You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 3a29a4d
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: src/content/articles/cdn-edge-caching-cache-keys-vary-headers/index.mdx
+36-18Lines changed: 36 additions & 18 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -18,15 +18,15 @@ The cache hit ratio looked fantastic. 94% of requests served from edge. Response
18
18
19
19
This is the fundamental tension in edge caching. Aggressive caching dramatically improves performance and reduces origin load, but incorrect configuration serves wrong content to users — sometimes catastrophically. Edge caching is a __data integrity__ problem first, performance problem second.
20
20
21
-
The good news: two concepts cause the vast majority of CDN cache bugs, and once you understand them, you can cache aggressively without fear. Those concepts are cache keys and Vary headers.
21
+
The good news: <Highlighter>two concepts cause the vast majority of CDN cache bugs, and once you understand them, you can cache aggressively without fear.</Highlighter> Those concepts are cache keys and Vary headers.
22
22
23
23
## Cache Keys Determine Correctness
24
24
25
25
A cache key is the identifier the CDN uses to store and retrieve cached responses. When a request arrives, the edge generates a cache key from the request attributes and looks for a matching entry. If found, it returns the cached response. If not, it fetches from origin and stores the response under that key.
26
26
27
27
By default, most CDNs use a simplified version of the URL:
28
28
29
-
```text
29
+
<Insetcolor="warning-inverse">
30
30
Default cache key:
31
31
SCHEME + HOST + PATH + QUERY_STRING
32
32
@@ -35,7 +35,7 @@ Example request:
35
35
36
36
Cache key:
37
37
"https://example.com/api/products?category=shoes"
38
-
```
38
+
</Inset>
39
39
40
40
This seems straightforward, but the details matter enormously. Two requests that __should__ return identical content but generate different cache keys create duplicate cache entries — wasting storage and reducing hit ratios. Two requests that __should__ return different content but generate the same cache key serve wrong content to users — exactly the bug we opened with.
41
41
@@ -45,15 +45,15 @@ The rule is simple: every attribute that affects your response must be in the ca
45
45
46
46
Query parameters are the most common source of cache key problems. Consider these two URLs:
47
47
48
-
```text
48
+
<Insetcolor="danger-inverse">
49
49
Problem: Query parameter order
50
50
51
51
These are the same request but different cache keys:
52
-
/products?color=red&size=large
53
-
/products?size=large&color=red
52
+
/products?color=red&size=large
53
+
/products?size=large&color=red
54
54
55
55
Result: Two cache entries for identical content
56
-
```
56
+
</Inset>
57
57
58
58
Different parameter order, different cache keys, same content — you're now storing two copies and reducing your hit ratio. Marketing teams compound this by adding tracking parameters to URLs. Every `utm_source`, `fbclid`, and `gclid` creates a unique cache key for content that's identical regardless of how the user arrived.
59
59
@@ -89,6 +89,8 @@ Query parameter pollution is one of the most common causes of poor cache hit rat
89
89
90
90
The `Vary` header tells caches "this response differs based on these request headers." It's the origin's way of saying "I returned different content to different clients based on header X, so you need to store separate cached versions."
91
91
92
+
<Newsletter />
93
+
92
94
When a CDN sees `Vary: Accept-Language`, it doesn't just store one cached response per URL — it stores one per URL __per unique Accept-Language value__. The cache key effectively becomes URL + the values of all headers listed in Vary.
93
95
94
96
```http title="vary-example.http"
@@ -105,7 +107,8 @@ With two headers in Vary, you get a combinatorial explosion of cache variants. E
105
107
The guideline: only vary on headers with a small, known set of values.
106
108
107
109
<Table
108
-
variant="vertical-column-delineation-table"
110
+
variant="grid-and-accent-header-table"
111
+
fullWidth={false}
109
112
content={{
110
113
figure: "Vary header guidance for common headers.",
@@ -138,8 +141,6 @@ The guideline: only vary on headers with a small, known set of values.
138
141
139
142
`Accept-Encoding` is safe because there are only a few compression algorithms. `Accept-Language` is risky because browsers send values like `en-US, en;q=0.9, de;q=0.8`—technically unique per user's language preferences. If you vary on it, normalize the header first to extract just the primary language.
140
143
141
-
<Newsletter />
142
-
143
144
### The Cookie Vary Trap
144
145
145
146
The most common Vary mistake is `Vary: Cookie`. It seems logical — if your response depends on cookies, tell the cache to vary on them. But cookies include session IDs, authentication tokens, and tracking identifiers that are unique per user. Even when the actual content-affecting cookie (`preferences=dark`) is the same, the unique session IDs create separate cache entries.
@@ -188,14 +189,31 @@ Understanding cache keys and Vary headers lets you avoid the two most dangerous
188
189
189
190
This is the bug from our opening story — serving one user's content to another. It happens when:
190
191
191
-
- Your origin returns `Cache-Control: public` for personalized responses
192
-
- You vary on session cookie but session IDs still contaminate the cache key
193
-
- You cache without checking for authentication headers
192
+
<List
193
+
variant="colored-marker-list"
194
+
classes={{
195
+
wrapper: "sm:ml-12 sm:mr-12 mb-6",
196
+
}}
197
+
items={[
198
+
{
199
+
color: "bg-danger-offset",
200
+
text: 'Your origin returns <span class="font-mono">Cache-Control: public</span> for personalized responses',
201
+
},
202
+
{
203
+
color: "bg-danger-offset",
204
+
text: "You vary on session cookie but session IDs still contaminate the cache key",
205
+
},
206
+
{
207
+
color: "bg-danger-offset",
208
+
text: "You cache without checking for authentication headers",
209
+
},
210
+
]}
211
+
/>
194
212
195
213
The patterns that keep you safe:
196
214
197
215
<Table
198
-
variant="vertical-column-delineation-table"
216
+
variant="criteria-highlight-table"
199
217
content={{
200
218
figure: "Personalized content caching patterns.",
201
219
thead: { th: ["Pattern", "Risk Level", "Why"] },
@@ -261,6 +279,10 @@ function handler(event: any): any {
261
279
262
280
With these headers normalized or stripped, your origin never sees the attack vectors that could poison your cache.
263
281
282
+
## Getting It Right
283
+
284
+
Both of these bugs — and most CDN issues you'll encounter — stem from the same root cause: a mismatch between what's in the cache key and what actually affects the response.
@@ -281,10 +303,6 @@ With these headers normalized or stripped, your origin never sees the attack vec
281
303
]}
282
304
/>
283
305
284
-
## Getting It Right
285
-
286
-
Both of these bugs — and most CDN issues you'll encounter — stem from the same root cause: a mismatch between what's in the cache key and what actually affects the response.
287
-
288
306
A properly-configured cache with 70% hit ratio beats a misconfigured cache with 95% hit ratio. The latter is serving wrong content to 95% of users — or worse, leaking private data between users.
289
307
290
308
Start with serving correct content: audit your cache keys, normalize your Vary headers, and verify that personalized content can never be cached. Once you've established that foundation, you can optimize for performance with confidence.
0 commit comments