-
Notifications
You must be signed in to change notification settings - Fork 351
Expand file tree
/
Copy pathcache-control.ts
More file actions
111 lines (94 loc) · 4.33 KB
/
Copy pathcache-control.ts
File metadata and controls
111 lines (94 loc) · 4.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import { getCdnCacheAdapter, type CdnCacheableHeaderInput } from "vinext/shims/cdn-cache";
export const NEVER_CACHE_CONTROL = "private, no-cache, no-store, max-age=0, must-revalidate";
export const BROWSER_REVALIDATE_CACHE_CONTROL = "public, max-age=0, must-revalidate";
export const STATIC_CACHE_CONTROL = "s-maxage=31536000, stale-while-revalidate";
// Matches Next.js's JSON-safe sentinel for `revalidate = false`.
export const INFINITE_CACHE_SECONDS = 0xfffffffe;
const STALE_REVALIDATE_CACHE_CONTROL = "s-maxage=0, stale-while-revalidate";
export const NO_STORE_CACHE_CONTROL = "no-store, must-revalidate";
const SHARED_CACHE_DIRECTIVE_RE = /(?:^|,)\s*s-maxage\s*=/i;
export function shouldUseNextDeployCacheControl(): boolean {
return process.env.VINEXT_NEXT_DEPLOY_CACHE_CONTROL === "1";
}
function isSharedCacheControl(cacheControl: string): boolean {
return SHARED_CACHE_DIRECTIVE_RE.test(cacheControl);
}
/**
* Route a cacheable response's headers through the active CDN cache adapter and
* apply the result to `headers`. The default adapter yields a single
* `Cache-Control` identical to `input.cacheControl` (no behavior change); edge
* adapters may instead emit `CDN-Cache-Control` / `Cache-Tag`.
*
* We only clear `Cache-Control` — the one header vinext stamps internally — so
* a stale vinext value never lingers if an adapter chooses not to emit one. The
* adapter's own headers are applied via `set()`, which overrides any prior value
* for the same name, so there's no need to pre-clear adapter-specific headers.
*/
export function applyCdnResponseHeaders(headers: Headers, input: CdnCacheableHeaderInput): void {
headers.delete("Cache-Control");
if (shouldUseNextDeployCacheControl() && isSharedCacheControl(input.cacheControl)) {
headers.set("Cache-Control", BROWSER_REVALIDATE_CACHE_CONTROL);
return;
}
const map = getCdnCacheAdapter().buildResponseHeaders(input);
for (const [name, value] of Object.entries(map)) {
if (value === null) {
headers.delete(name);
continue;
}
// Never stamp an empty header. An adapter returns an empty `Cache-Control`
// only when it has no default for an empty policy (e.g. the default
// origin-managed adapter), in which case the header should stay absent
// rather than being emitted as a blank value.
if (value === "") continue;
headers.set(name, value);
}
}
/**
* Matches Next.js's `getCacheControlHeader` stale window semantics while
* preserving vinext's legacy unbounded SWR header when no expire ceiling is
* available yet.
*
* Next.js source:
* https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/cache-control.ts
*/
export function buildRevalidateCacheControl(
revalidateSeconds: number,
expireSeconds?: number,
): string {
if (expireSeconds === undefined) {
return `s-maxage=${revalidateSeconds}, stale-while-revalidate`;
}
// `expire <= revalidate` is a zero-width stale window: downstream caches
// should refetch after s-maxage instead of serving stale.
if (revalidateSeconds >= expireSeconds) {
return `s-maxage=${revalidateSeconds}`;
}
return `s-maxage=${revalidateSeconds}, stale-while-revalidate=${
expireSeconds - revalidateSeconds
}`;
}
/**
* Builds Cache-Control for ISR cache reads. HIT responses and STALE responses
* with stored expire metadata use the same route policy because Next.js derives
* this header from cache-control metadata, not from the cache hit/stale state.
* STALE entries without expire metadata keep vinext's legacy `s-maxage=0`
* fallback so older cache entries are not treated as newly fresh downstream.
*/
export function buildCachedRevalidateCacheControl(
cacheState: "HIT" | "STALE",
revalidateSeconds: number,
expireSeconds?: number,
): string {
if (revalidateSeconds === Infinity || revalidateSeconds === INFINITE_CACHE_SECONDS) {
return STATIC_CACHE_CONTROL;
}
// When expire is known, match Next.js and emit the route policy even for
// vinext-served STALE entries. The hard-expire gate has already decided the
// stale payload is still usable, and downstream caches should see the same
// finite SWR window Next.js would emit from cacheControl metadata.
if (cacheState === "STALE" && expireSeconds === undefined) {
return STALE_REVALIDATE_CACHE_CONTROL;
}
return buildRevalidateCacheControl(revalidateSeconds, expireSeconds);
}