[next] fix content-type for RSC prefetches (#10487)

This ensures that the `.prefetch.rsc` requests respond with the correct `content-type` since this is used by Next.js to determine if a request is valid or not (and in the case it's invalid, an mpa navigation will occur)

Fixes: https://github.com/vercel/next.js/issues/54934
This commit is contained in:
Zack Tanner
2023-09-11 12:04:16 -07:00
committed by GitHub
parent 8504735808
commit a732d30c84
4 changed files with 35 additions and 8 deletions

View File

@@ -0,0 +1,5 @@
---
"@vercel/next": patch
---
fix content-type for RSC prefetches

View File

@@ -45,6 +45,8 @@ import {
UnwrapPromise, UnwrapPromise,
getOperationType, getOperationType,
FunctionsConfigManifestV1, FunctionsConfigManifestV1,
RSC_CONTENT_TYPE,
RSC_PREFETCH_SUFFIX,
} from './utils'; } from './utils';
import { import {
nodeFileTrace, nodeFileTrace,
@@ -168,7 +170,6 @@ export async function serverBuild({
} }
} }
const APP_PREFETCH_SUFFIX = '.prefetch.rsc';
let appRscPrefetches: UnwrapPromise<ReturnType<typeof glob>> = {}; let appRscPrefetches: UnwrapPromise<ReturnType<typeof glob>> = {};
let appBuildTraces: UnwrapPromise<ReturnType<typeof glob>> = {}; let appBuildTraces: UnwrapPromise<ReturnType<typeof glob>> = {};
let appDir: string | null = null; let appDir: string | null = null;
@@ -176,7 +177,18 @@ export async function serverBuild({
if (appPathRoutesManifest) { if (appPathRoutesManifest) {
appDir = path.join(pagesDir, '../app'); appDir = path.join(pagesDir, '../app');
appBuildTraces = await glob('**/*.js.nft.json', appDir); appBuildTraces = await glob('**/*.js.nft.json', appDir);
appRscPrefetches = await glob(`**/*${APP_PREFETCH_SUFFIX}`, appDir); appRscPrefetches = await glob(`**/*${RSC_PREFETCH_SUFFIX}`, appDir);
const rscContentTypeHeader =
routesManifest?.rsc?.contentTypeHeader || RSC_CONTENT_TYPE;
// ensure all appRscPrefetches have a contentType since this is used by Next.js
// to determine if it's a valid response
for (const value of Object.values(appRscPrefetches)) {
if (!value.contentType) {
value.contentType = rscContentTypeHeader;
}
}
} }
const isCorrectNotFoundRoutes = semver.gte( const isCorrectNotFoundRoutes = semver.gte(
@@ -1526,7 +1538,7 @@ export async function serverBuild({
dest: path.posix.join( dest: path.posix.join(
'/', '/',
entryDirectory, entryDirectory,
'/index.prefetch.rsc' `/index${RSC_PREFETCH_SUFFIX}`
), ),
headers: { vary: rscVaryHeader }, headers: { vary: rscVaryHeader },
continue: true, continue: true,
@@ -1547,7 +1559,7 @@ export async function serverBuild({
dest: path.posix.join( dest: path.posix.join(
'/', '/',
entryDirectory, entryDirectory,
`/$1${APP_PREFETCH_SUFFIX}` `/$1${RSC_PREFETCH_SUFFIX}`
), ),
headers: { vary: rscVaryHeader }, headers: { vary: rscVaryHeader },
continue: true, continue: true,
@@ -1626,7 +1638,7 @@ export async function serverBuild({
src: path.posix.join( src: path.posix.join(
'/', '/',
entryDirectory, entryDirectory,
`/index${APP_PREFETCH_SUFFIX}` `/index${RSC_PREFETCH_SUFFIX}`
), ),
dest: path.posix.join('/', entryDirectory, '/index.rsc'), dest: path.posix.join('/', entryDirectory, '/index.rsc'),
has: [ has: [
@@ -1642,7 +1654,7 @@ export async function serverBuild({
src: `^${path.posix.join( src: `^${path.posix.join(
'/', '/',
entryDirectory, entryDirectory,
`/(.+?)${APP_PREFETCH_SUFFIX}(?:/)?$` `/(.+?)${RSC_PREFETCH_SUFFIX}(?:/)?$`
)}`, )}`,
dest: path.posix.join('/', entryDirectory, '/$1.rsc'), dest: path.posix.join('/', entryDirectory, '/$1.rsc'),
has: [ has: [

View File

@@ -48,6 +48,9 @@ export const MIB = 1024 * KIB;
export const prettyBytes = (n: number) => bytes(n, { unitSeparator: ' ' }); export const prettyBytes = (n: number) => bytes(n, { unitSeparator: ' ' });
export const RSC_CONTENT_TYPE = 'x-component';
export const RSC_PREFETCH_SUFFIX = '.prefetch.rsc';
// Identify /[param]/ in route string // Identify /[param]/ in route string
// eslint-disable-next-line no-useless-escape // eslint-disable-next-line no-useless-escape
const TEST_DYNAMIC_ROUTE = /\/\[[^\/]+?\](?=\/|$)/; const TEST_DYNAMIC_ROUTE = /\/\[[^\/]+?\](?=\/|$)/;
@@ -2158,7 +2161,7 @@ export const onPrerenderRoute =
routesManifest?.rsc?.varyHeader || routesManifest?.rsc?.varyHeader ||
'RSC, Next-Router-State-Tree, Next-Router-Prefetch'; 'RSC, Next-Router-State-Tree, Next-Router-Prefetch';
const rscContentTypeHeader = const rscContentTypeHeader =
routesManifest?.rsc?.contentTypeHeader || 'text/x-component'; routesManifest?.rsc?.contentTypeHeader || RSC_CONTENT_TYPE;
let sourcePath: string | undefined; let sourcePath: string | undefined;
if (`/${outputPathPage}` !== srcRoute && srcRoute) { if (`/${outputPathPage}` !== srcRoute && srcRoute) {

View File

@@ -44,6 +44,9 @@
"RSC": "1", "RSC": "1",
"Next-Router-Prefetch": "1" "Next-Router-Prefetch": "1"
}, },
"responseHeaders": {
"content-type":"text/x-component"
},
"mustContain": ":", "mustContain": ":",
"mustNotContain": "<html" "mustNotContain": "<html"
}, },
@@ -72,6 +75,9 @@
"headers": { "headers": {
"Next-Router-Prefetch": "1", "Next-Router-Prefetch": "1",
"RSC": "1" "RSC": "1"
},
"responseHeaders": {
"content-type":"text/x-component"
} }
}, },
{ {
@@ -99,7 +105,8 @@
"path": "/ssg", "path": "/ssg",
"status": 200, "status": 200,
"responseHeaders": { "responseHeaders": {
"vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url" "vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url",
"content-type":"text/x-component"
}, },
"headers": { "headers": {
"RSC": "1", "RSC": "1",