File size: 3,321 Bytes
b91e262
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import type { ServerResponse } from 'http'
import { CACHE_ONE_YEAR } from '../../lib/constants'

/**
 * The revalidate option used internally for pages. A value of `false` means
 * that the page should not be revalidated. A number means that the page
 * should be revalidated after the given number of seconds (this also includes
 * `1` which means to revalidate after 1 second). A value of `0` is not a valid
 * value for this option.
 */
export type Revalidate = number | false

export interface CacheControl {
  revalidate: Revalidate
  expire: number | undefined
}

export interface CacheHeaders {
  'Cache-Control': string
  cdnCacheControl?: string
}

export function getCacheControlHeader({
  revalidate,
  expire,
}: CacheControl): CacheHeaders {
  const swrHeader =
    typeof revalidate === 'number' &&
    expire !== undefined &&
    revalidate < expire
      ? `, stale-while-revalidate=${expire - revalidate}`
      : ''

  if (revalidate === 0) {
    return {
      'Cache-Control':
        'private, no-cache, no-store, max-age=0, must-revalidate',
    }
  }

  // For non-zero revalidation, we want to leverage CDN stale-while-revalidate caching
  // semantics without allowing the browser to cache the response.
  const maxAge = typeof revalidate === 'number' ? revalidate : CACHE_ONE_YEAR
  const cdnCacheControl = `max-age=${maxAge}${swrHeader}`
  const cacheControl = `s-maxage=${maxAge}`

  return {
    'Cache-Control': cacheControl,
    cdnCacheControl: cdnCacheControl,
  }
}

/**
 * The default header name used for CDN cache control.
 */
export const DEFAULT_CDN_CACHE_CONTROL_HEADER = 'CDN-Cache-Control'

/**
 * Sets cache control headers on a ServerResponse object.
 * Use this helper to consistently set Cache-Control and CDN cache control headers.
 *
 * @param res - The ServerResponse object
 * @param cacheControl - The cache control configuration
 * @param cdnCacheControlHeader - Custom CDN cache control header name from config, falls back to 'CDN-Cache-Control' if undefined
 */
export function setResponseCacheControlHeaders(
  res: ServerResponse,
  cacheControl: CacheControl,
  cdnCacheControlHeader: string | undefined
): void {
  const cacheHeaders = getCacheControlHeader(cacheControl)
  const headerName = cdnCacheControlHeader ?? DEFAULT_CDN_CACHE_CONTROL_HEADER
  res.setHeader('Cache-Control', cacheHeaders['Cache-Control'])
  if (cacheHeaders.cdnCacheControl) {
    res.setHeader(headerName, cacheHeaders.cdnCacheControl)
  }
}

/**
 * Sets cache control headers on a Headers object (for Web API responses).
 * Use this helper to consistently set Cache-Control and CDN cache control headers.
 *
 * @param headers - The Headers object
 * @param cacheControl - The cache control configuration
 * @param cdnCacheControlHeader - Custom CDN cache control header name from config, falls back to 'CDN-Cache-Control' if undefined
 */
export function setCacheControlHeaders(
  headers: Headers,
  cacheControl: CacheControl,
  cdnCacheControlHeader: string | undefined
): void {
  const cacheHeaders = getCacheControlHeader(cacheControl)
  const headerName = cdnCacheControlHeader ?? DEFAULT_CDN_CACHE_CONTROL_HEADER
  headers.set('Cache-Control', cacheHeaders['Cache-Control'])
  if (cacheHeaders.cdnCacheControl) {
    headers.set(headerName, cacheHeaders.cdnCacheControl)
  }
}