Skip to content

Commit 57d8d26

Browse files
authoredApr 17, 2024
fix: honor user defined netlify-vary (#410)
1 parent e087bf7 commit 57d8d26

File tree

2 files changed

+108
-16
lines changed

2 files changed

+108
-16
lines changed
 

‎src/run/headers.test.ts

+47
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,53 @@ describe('headers', () => {
140140
'header=x-nextjs-data,language=en|de|fr,cookie=__prerender_bypass|__next_preview_data|NEXT_LOCALE',
141141
)
142142
})
143+
144+
test('with user defined Netlify-Vary (catch-all query) being included', () => {
145+
const headers = new Headers({
146+
'Netlify-Vary': 'query,header=x-custom-header,language=es,country=es,cookie=ab_test',
147+
})
148+
const request = new Request(`${defaultUrl}/base/path`)
149+
const config = {
150+
...defaultConfig,
151+
basePath: '/base/path',
152+
i18n: {
153+
locales: ['en', 'de', 'fr'],
154+
defaultLocale: 'default',
155+
},
156+
}
157+
vi.spyOn(headers, 'set')
158+
159+
setVaryHeaders(headers, request, config)
160+
161+
expect(headers.set).toBeCalledWith(
162+
'netlify-vary',
163+
'query,header=x-nextjs-data|x-custom-header,language=en|de|fr|es,cookie=__prerender_bypass|__next_preview_data|NEXT_LOCALE|ab_test,country=es',
164+
)
165+
})
166+
167+
test('with user defined Netlify-Vary (manual query variation) being included', () => {
168+
const headers = new Headers({
169+
'Netlify-Vary':
170+
'query=item_id|page|per_page,header=x-custom-header,language=es,country=es,cookie=ab_test',
171+
})
172+
const request = new Request(`${defaultUrl}/base/path`)
173+
const config = {
174+
...defaultConfig,
175+
basePath: '/base/path',
176+
i18n: {
177+
locales: ['en', 'de', 'fr'],
178+
defaultLocale: 'default',
179+
},
180+
}
181+
vi.spyOn(headers, 'set')
182+
183+
setVaryHeaders(headers, request, config)
184+
185+
expect(headers.set).toBeCalledWith(
186+
'netlify-vary',
187+
'query=item_id|page|per_page,header=x-nextjs-data|x-custom-header,language=en|de|fr|es,cookie=__prerender_bypass|__next_preview_data|NEXT_LOCALE|ab_test,country=es',
188+
)
189+
})
143190
})
144191
})
145192

‎src/run/headers.ts

+61-16
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,48 @@ import type { TagsManifest } from './config.js'
88
import type { RequestContext } from './handlers/request-context.cjs'
99
import type { RuntimeTracer } from './handlers/tracer.cjs'
1010

11+
const ALL_VARIATIONS = Symbol.for('ALL_VARIATIONS')
1112
interface NetlifyVaryValues {
12-
headers: string[]
13-
languages: string[]
14-
cookies: string[]
13+
header: string[]
14+
language: string[]
15+
cookie: string[]
16+
/**
17+
* Query variation can be without argument in which case all query combinations would create a new cache key
18+
* This is represented by a ALL_VARIATIONS in the array.
19+
*/
20+
query: (string | typeof ALL_VARIATIONS)[]
21+
country: string[]
1522
}
1623

17-
const generateNetlifyVaryValues = ({ headers, languages, cookies }: NetlifyVaryValues): string => {
24+
const NetlifyVaryKeys = new Set(['header', 'language', 'cookie', 'query', 'country'])
25+
const isNetlifyVaryKey = (key: string): key is keyof NetlifyVaryValues => NetlifyVaryKeys.has(key)
26+
27+
const generateNetlifyVaryValues = ({
28+
header,
29+
language,
30+
cookie,
31+
query,
32+
country,
33+
}: NetlifyVaryValues): string => {
1834
const values: string[] = []
19-
if (headers.length !== 0) {
20-
values.push(`header=${headers.join(`|`)}`)
35+
if (query.length !== 0) {
36+
if (query.includes(ALL_VARIATIONS)) {
37+
values.push(`query`)
38+
} else {
39+
values.push(`query=${query.join(`|`)}`)
40+
}
41+
}
42+
if (header.length !== 0) {
43+
values.push(`header=${header.join(`|`)}`)
44+
}
45+
if (language.length !== 0) {
46+
values.push(`language=${language.join(`|`)}`)
2147
}
22-
if (languages.length !== 0) {
23-
values.push(`language=${languages.join(`|`)}`)
48+
if (cookie.length !== 0) {
49+
values.push(`cookie=${cookie.join(`|`)}`)
2450
}
25-
if (cookies.length !== 0) {
26-
values.push(`cookie=${cookies.join(`|`)}`)
51+
if (country.length !== 0) {
52+
values.push(`country=${country.join(`|`)}`)
2753
}
2854
return values.join(',')
2955
}
@@ -56,22 +82,40 @@ export const setVaryHeaders = (
5682
{ basePath, i18n }: Pick<NextConfigComplete, 'basePath' | 'i18n'>,
5783
) => {
5884
const netlifyVaryValues: NetlifyVaryValues = {
59-
headers: ['x-nextjs-data'],
60-
languages: [],
61-
cookies: ['__prerender_bypass', '__next_preview_data'],
85+
header: ['x-nextjs-data'],
86+
language: [],
87+
cookie: ['__prerender_bypass', '__next_preview_data'],
88+
query: [],
89+
country: [],
6290
}
6391

6492
const vary = headers.get('vary')
6593
if (vary !== null) {
66-
netlifyVaryValues.headers.push(...getHeaderValueArray(vary))
94+
netlifyVaryValues.header.push(...getHeaderValueArray(vary))
6795
}
6896

6997
const path = new URL(request.url).pathname
7098
const locales = i18n && i18n.localeDetection !== false ? i18n.locales : []
7199

72100
if (locales.length > 1 && (path === '/' || path === basePath)) {
73-
netlifyVaryValues.languages.push(...locales)
74-
netlifyVaryValues.cookies.push(`NEXT_LOCALE`)
101+
netlifyVaryValues.language.push(...locales)
102+
netlifyVaryValues.cookie.push(`NEXT_LOCALE`)
103+
}
104+
105+
const userNetlifyVary = headers.get('netlify-vary')
106+
if (userNetlifyVary) {
107+
// respect user's header and append them
108+
const directives = getHeaderValueArray(userNetlifyVary)
109+
for (const directive of directives) {
110+
const [key, value] = directive.split('=')
111+
112+
if (key === 'query' && !value) {
113+
// query can have no "assignment" and then it should vary on all possible query combinations
114+
netlifyVaryValues.query.push(ALL_VARIATIONS)
115+
} else if (value && isNetlifyVaryKey(key)) {
116+
netlifyVaryValues[key].push(...value.split('|'))
117+
}
118+
}
75119
}
76120

77121
headers.set(`netlify-vary`, generateNetlifyVaryValues(netlifyVaryValues))
@@ -182,6 +226,7 @@ export const setCacheControlHeaders = (
182226
if (
183227
typeof requestContext.routeHandlerRevalidate !== 'undefined' &&
184228
['GET', 'HEAD'].includes(request.method) &&
229+
!headers.has('cdn-cache-control') &&
185230
!headers.has('netlify-cdn-cache-control')
186231
) {
187232
// handle CDN Cache Control on Route Handler responses

0 commit comments

Comments
 (0)
Please sign in to comment.