-
Notifications
You must be signed in to change notification settings - Fork 66.9k
Expand file tree
/
Copy pathcache-control.ts
More file actions
107 lines (96 loc) · 3.54 KB
/
cache-control.ts
File metadata and controls
107 lines (96 loc) · 3.54 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
import type { Response } from 'express'
interface CacheControlOptions {
key?: string
public_?: boolean
immutable?: boolean
maxAgeZero?: boolean
}
// Return a function you can pass a Response object to and it will
// set the `Cache-Control` header.
//
// For example:
//
// const cacheControlYear = getCacheControl(60 * 60 * 24 * 365)
// ...
// cacheControlYear(res)
// res.send(body)
//
// Max age is in seconds
// Max age should not be greater than 31536000 https://www.ietf.org/rfc/rfc2616.txt
function cacheControlFactory(
maxAge: number = 60 * 60,
{
key = 'cache-control',
public_ = true,
immutable = false,
maxAgeZero = false,
}: CacheControlOptions = {},
): (res: Response) => void {
const directives = [
maxAge && public_ && 'public',
maxAge && `max-age=${maxAge}`,
maxAge && immutable && 'immutable',
!maxAge && 'private',
!maxAge && 'no-store',
maxAge >= 60 * 60 && `stale-while-revalidate=${60 * 60}`,
maxAge >= 60 * 60 && `stale-if-error=${24 * 60 * 60}`,
(maxAgeZero || maxAge === 0) && 'max-age=0',
]
.filter(Boolean)
.join(', ')
return (res: Response) => {
if (process.env.NODE_ENV !== 'production' && res.hasHeader('set-cookie') && maxAge) {
console.warn(
"You can't set a >0 cache-control header AND set-cookie or else the CDN will never respect the cache-control.",
)
}
res.set(key, directives)
}
}
// These are roughly in order from shortest to longest
// If you do not want caching
export const noCacheControl = cacheControlFactory(0)
// Short cache for 4xx errors
export const errorCacheControl = cacheControlFactory(60) // 1 minute
// This means we tell the browser to cache the XHR request for 1h
const searchBrowserCacheControl = cacheControlFactory(60 * 60)
// This tells the CDN to cache the response for 24 hours
const searchCdnCacheControl = cacheControlFactory(60 * 60 * 24, {
key: 'surrogate-control',
})
export function searchCacheControl(res: Response): void {
searchBrowserCacheControl(res)
searchCdnCacheControl(res)
}
// 24 hours for CDN, we soft-purge this with each deploy
const defaultCDNCacheControl = cacheControlFactory(60 * 60 * 24, {
key: 'surrogate-control',
})
// Shorter because between deployments and their (sort) purges,
// we don't want the browser to overly cache because with them we
// can't control purging.
const defaultBrowserCacheControl = cacheControlFactory(60)
// A general default configuration that is useful to almost all responses
// that can be cached.
export function defaultCacheControl(res: Response): void {
defaultCDNCacheControl(res)
defaultBrowserCacheControl(res)
}
// Vary on language when needed
// x-user-language is a custom request header derived from req.cookie:user_language
// accept-language is truncated to one of our available languages
// https://bit.ly/3u5UeRN
export function languageCacheControl(res: Response): void {
defaultCacheControl(res)
res.set('vary', 'accept-language, x-user-language')
}
// Vary on both language and version for homepage redirects
// x-user-version is a custom request header derived from req.cookie:user_version
export function languageAndVersionCacheControl(res: Response): void {
defaultCacheControl(res)
res.set('vary', 'accept-language, x-user-language, x-user-version')
}
// Long cache control for versioned assets: images, CSS, JS...
export const assetCacheControl = cacheControlFactory(60 * 60 * 24 * 7, { immutable: true })
// Long caching for archived pages and assets
export const archivedCacheControl = cacheControlFactory(60 * 60 * 24 * 365)