Skip to content

Commit 362d25d

Browse files
committed
fix: RSC responses when using middleware rewrites or redirects for cacheable page being served for html requests
1 parent 5e14009 commit 362d25d

File tree

2 files changed

+64
-10
lines changed

2 files changed

+64
-10
lines changed

src/run/headers.test.ts

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ describe('headers', () => {
4040

4141
expect(headers.set).toBeCalledWith(
4242
'netlify-vary',
43-
'query=__nextDataReq|_rsc,header=x-nextjs-data|x-next-debug-logging,cookie=__prerender_bypass|__next_preview_data',
43+
'query=__nextDataReq|_rsc,header=x-nextjs-data|x-next-debug-logging|next-router-prefetch|next-router-segment-prefetch|next-router-state-tree|next-url|rsc,cookie=__prerender_bypass|__next_preview_data',
4444
)
4545
})
4646

@@ -56,7 +56,7 @@ describe('headers', () => {
5656

5757
expect(headers.set).toBeCalledWith(
5858
'netlify-vary',
59-
'query=__nextDataReq|_rsc,header=x-nextjs-data|x-next-debug-logging|Accept|Accept-Language,cookie=__prerender_bypass|__next_preview_data',
59+
'query=__nextDataReq|_rsc,header=x-nextjs-data|x-next-debug-logging|next-router-prefetch|next-router-segment-prefetch|next-router-state-tree|next-url|rsc|accept|accept-language,cookie=__prerender_bypass|__next_preview_data',
6060
)
6161
})
6262

@@ -77,7 +77,7 @@ describe('headers', () => {
7777

7878
expect(headers.set).toBeCalledWith(
7979
'netlify-vary',
80-
'query=__nextDataReq|_rsc,header=x-nextjs-data|x-next-debug-logging,cookie=__prerender_bypass|__next_preview_data',
80+
'query=__nextDataReq|_rsc,header=x-nextjs-data|x-next-debug-logging|next-router-prefetch|next-router-segment-prefetch|next-router-state-tree|next-url|rsc,cookie=__prerender_bypass|__next_preview_data',
8181
)
8282
})
8383

@@ -97,7 +97,7 @@ describe('headers', () => {
9797

9898
expect(headers.set).toBeCalledWith(
9999
'netlify-vary',
100-
'query=__nextDataReq|_rsc,header=x-nextjs-data|x-next-debug-logging,cookie=__prerender_bypass|__next_preview_data',
100+
'query=__nextDataReq|_rsc,header=x-nextjs-data|x-next-debug-logging|next-router-prefetch|next-router-segment-prefetch|next-router-state-tree|next-url|rsc,cookie=__prerender_bypass|__next_preview_data',
101101
)
102102
})
103103

@@ -117,7 +117,7 @@ describe('headers', () => {
117117

118118
expect(headers.set).toBeCalledWith(
119119
'netlify-vary',
120-
'query=__nextDataReq|_rsc,header=x-nextjs-data|x-next-debug-logging,language=en|de|fr,cookie=__prerender_bypass|__next_preview_data|NEXT_LOCALE',
120+
'query=__nextDataReq|_rsc,header=x-nextjs-data|x-next-debug-logging|next-router-prefetch|next-router-segment-prefetch|next-router-state-tree|next-url|rsc,language=en|de|fr,cookie=__prerender_bypass|__next_preview_data|NEXT_LOCALE',
121121
)
122122
})
123123

@@ -138,7 +138,7 @@ describe('headers', () => {
138138

139139
expect(headers.set).toBeCalledWith(
140140
'netlify-vary',
141-
'query=__nextDataReq|_rsc,header=x-nextjs-data|x-next-debug-logging,language=en|de|fr,cookie=__prerender_bypass|__next_preview_data|NEXT_LOCALE',
141+
'query=__nextDataReq|_rsc,header=x-nextjs-data|x-next-debug-logging|next-router-prefetch|next-router-segment-prefetch|next-router-state-tree|next-url|rsc,language=en|de|fr,cookie=__prerender_bypass|__next_preview_data|NEXT_LOCALE',
142142
)
143143
})
144144

@@ -161,7 +161,7 @@ describe('headers', () => {
161161

162162
expect(headers.set).toBeCalledWith(
163163
'netlify-vary',
164-
'query,header=x-nextjs-data|x-next-debug-logging|x-custom-header,language=en|de|fr|es,cookie=__prerender_bypass|__next_preview_data|NEXT_LOCALE|ab_test,country=es',
164+
'query,header=x-nextjs-data|x-next-debug-logging|next-router-prefetch|next-router-segment-prefetch|next-router-state-tree|next-url|rsc|x-custom-header,language=en|de|fr|es,cookie=__prerender_bypass|__next_preview_data|NEXT_LOCALE|ab_test,country=es',
165165
)
166166
})
167167

@@ -185,10 +185,44 @@ describe('headers', () => {
185185

186186
expect(headers.set).toBeCalledWith(
187187
'netlify-vary',
188-
'query=__nextDataReq|_rsc|item_id|page|per_page,header=x-nextjs-data|x-next-debug-logging|x-custom-header,language=en|de|fr|es,cookie=__prerender_bypass|__next_preview_data|NEXT_LOCALE|ab_test,country=es',
188+
'query=__nextDataReq|_rsc|item_id|page|per_page,header=x-nextjs-data|x-next-debug-logging|next-router-prefetch|next-router-segment-prefetch|next-router-state-tree|next-url|rsc|x-custom-header,language=en|de|fr|es,cookie=__prerender_bypass|__next_preview_data|NEXT_LOCALE|ab_test,country=es',
189189
)
190190
})
191191
})
192+
193+
test('with vary headers provided by Next.js before 15.3.0', () => {
194+
const headers = new Headers({
195+
// before https://github.com/vercel/next.js/pull/77797 Next.js was producing following headers
196+
Vary: 'RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Next-Url',
197+
})
198+
const request = new Request(defaultUrl)
199+
vi.spyOn(headers, 'set')
200+
201+
setVaryHeaders(headers, request, defaultConfig)
202+
203+
expect(headers.set).toBeCalledWith(
204+
'netlify-vary',
205+
'query=__nextDataReq|_rsc,header=x-nextjs-data|x-next-debug-logging|next-router-prefetch|next-router-segment-prefetch|next-router-state-tree|next-url|rsc,cookie=__prerender_bypass|__next_preview_data',
206+
)
207+
})
208+
209+
test('with vary headers provided by Next.js before 15.3.0 and user defined Netlify-vary', () => {
210+
const headers = new Headers({
211+
// before https://github.com/vercel/next.js/pull/77797 Next.js was producing following headers
212+
Vary: 'RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Next-Url',
213+
'Netlify-Vary':
214+
'query=item_id|page|per_page,header=x-custom-header,language=es,country=es,cookie=ab_test',
215+
})
216+
const request = new Request(defaultUrl)
217+
vi.spyOn(headers, 'set')
218+
219+
setVaryHeaders(headers, request, defaultConfig)
220+
221+
expect(headers.set).toBeCalledWith(
222+
'netlify-vary',
223+
'query=__nextDataReq|_rsc|item_id|page|per_page,header=x-nextjs-data|x-next-debug-logging|next-router-prefetch|next-router-segment-prefetch|next-router-state-tree|next-url|rsc|x-custom-header,language=es,cookie=__prerender_bypass|__next_preview_data|ab_test,country=es',
224+
)
225+
})
192226
})
193227

194228
describe('setCacheControlHeaders', () => {

src/run/headers.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,15 @@ const generateNetlifyVaryValues = ({
3939
}
4040
}
4141
if (header.length !== 0) {
42-
values.push(`header=${header.join(`|`)}`)
42+
const uniqueHeaderNames = [
43+
...new Set(
44+
header.map((headerName) =>
45+
// header names are case insensitive
46+
headerName.toLowerCase(),
47+
),
48+
),
49+
]
50+
values.push(`header=${uniqueHeaderNames.join(`|`)}`)
4351
}
4452
if (language.length !== 0) {
4553
values.push(`language=${language.join(`|`)}`)
@@ -78,7 +86,19 @@ export const setVaryHeaders = (
7886
{ basePath, i18n }: Pick<NextConfigComplete, 'basePath' | 'i18n'>,
7987
) => {
8088
const netlifyVaryValues: NetlifyVaryValues = {
81-
header: ['x-nextjs-data', 'x-next-debug-logging'],
89+
header: [
90+
'x-nextjs-data',
91+
'x-next-debug-logging',
92+
// using _rsc query param might not be enough because it is stripped for middleware redirect and rewrites
93+
// so adding all request headers that are used to produce the _rsc query param
94+
// https://github.com/vercel/next.js/blob/e5fe535ed17cee5e1d5576ccc33e4c49b5da1273/packages/next/src/client/components/router-reducer/set-cache-busting-search-param.ts#L32-L39
95+
'Next-Router-Prefetch',
96+
'Next-Router-Segment-Prefetch',
97+
'Next-Router-State-Tree',
98+
'Next-Url',
99+
// and exact header that actually instruct Next.js to produce RSC response
100+
'RSC',
101+
],
82102
language: [],
83103
cookie: ['__prerender_bypass', '__next_preview_data'],
84104
query: ['__nextDataReq', '_rsc'],

0 commit comments

Comments
 (0)