mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-06 04:22:01 +00:00
[next] fix re-mapping logic for index prefetches (#10750)
Follow-up to https://github.com/vercel/vercel/pull/10734 -- but considers that the static prefetch associated with `/` might be inside of a dir such as `index/index.prefetch.rsc`. To avoid any future matching conflicts, this PR updates to prefix all static prefetches
This commit is contained in:
5
.changeset/dry-plants-juggle.md
Normal file
5
.changeset/dry-plants-juggle.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@vercel/next": patch
|
||||
---
|
||||
|
||||
fix re-mapping logic for index prefetches
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
VariantsManifest,
|
||||
RSC_CONTENT_TYPE,
|
||||
RSC_PREFETCH_SUFFIX,
|
||||
normalizePrefetches,
|
||||
} from './utils';
|
||||
import {
|
||||
nodeFileTrace,
|
||||
@@ -193,13 +194,7 @@ export async function serverBuild({
|
||||
const rscContentTypeHeader =
|
||||
routesManifest?.rsc?.contentTypeHeader || RSC_CONTENT_TYPE;
|
||||
|
||||
// index.{ext} outputs get mapped to `/` which we don't want to override
|
||||
// dynamic routes that aren't pregenerated like the prefetch rsc payload
|
||||
if (appRscPrefetches['index.prefetch.rsc']) {
|
||||
appRscPrefetches['__index.prefetch.rsc'] =
|
||||
appRscPrefetches['index.prefetch.rsc'];
|
||||
delete appRscPrefetches['index.prefetch.rsc'];
|
||||
}
|
||||
appRscPrefetches = normalizePrefetches(appRscPrefetches);
|
||||
|
||||
// ensure all appRscPrefetches have a contentType since this is used by Next.js
|
||||
// to determine if it's a valid response
|
||||
@@ -1647,7 +1642,7 @@ export async function serverBuild({
|
||||
dest: path.posix.join(
|
||||
'/',
|
||||
entryDirectory,
|
||||
`/$1${RSC_PREFETCH_SUFFIX}`
|
||||
`/__$1${RSC_PREFETCH_SUFFIX}`
|
||||
),
|
||||
headers: { vary: rscVaryHeader },
|
||||
continue: true,
|
||||
@@ -1742,7 +1737,7 @@ export async function serverBuild({
|
||||
src: `^${path.posix.join(
|
||||
'/',
|
||||
entryDirectory,
|
||||
`/(.+?)${RSC_PREFETCH_SUFFIX}(?:/)?$`
|
||||
`/__(.+?)${RSC_PREFETCH_SUFFIX}(?:/)?$`
|
||||
)}`,
|
||||
dest: path.posix.join('/', entryDirectory, '/$1.rsc'),
|
||||
has: [
|
||||
|
||||
@@ -3120,3 +3120,16 @@ export async function getServerlessPages(params: {
|
||||
|
||||
return { pages, appPaths: normalizedAppPaths };
|
||||
}
|
||||
|
||||
// to avoid any conflict with route matching/resolving, we prefix all prefetches (ie, __index.prefetch.rsc)
|
||||
// this is to ensure that prefetches are never matched for things like a greedy match on `index.{ext}`
|
||||
export function normalizePrefetches(prefetches: Record<string, FileFsRef>) {
|
||||
const updatedPrefetches: Record<string, FileFsRef> = {};
|
||||
|
||||
for (const key in prefetches) {
|
||||
const newKey = key.replace(/([^/]+\.prefetch\.rsc)$/, '__$1');
|
||||
updatedPrefetches[newKey] = prefetches[key];
|
||||
}
|
||||
|
||||
return updatedPrefetches;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function Page(props) {
|
||||
return (
|
||||
<div>
|
||||
<p>catch-all {JSON.stringify(props.params || {})}</p>
|
||||
)
|
||||
<Link href="/">Link to /</Link>
|
||||
<Link href="/index">Link to /index</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
return [
|
||||
{
|
||||
slug: ['']
|
||||
slug: [''],
|
||||
},
|
||||
{
|
||||
slug: ['first']
|
||||
}
|
||||
]
|
||||
slug: ['index'],
|
||||
},
|
||||
{
|
||||
slug: ['first'],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export const revalidate = 0
|
||||
export const revalidate = 0;
|
||||
|
||||
27
packages/next/test/fixtures/00-app-dir-root-catch-all/app/nested/[[...slug]]/page.js
vendored
Normal file
27
packages/next/test/fixtures/00-app-dir-root-catch-all/app/nested/[[...slug]]/page.js
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function Page(props) {
|
||||
return (
|
||||
<div>
|
||||
<p>catch-all {JSON.stringify(props.params || {})}</p>
|
||||
<Link href="/nested">Link to /</Link>
|
||||
<Link href="/nested/index">Link to /index</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
return [
|
||||
{
|
||||
slug: [''],
|
||||
},
|
||||
{
|
||||
slug: ['index'],
|
||||
},
|
||||
{
|
||||
slug: ['first'],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export const revalidate = 0;
|
||||
@@ -25,6 +25,63 @@
|
||||
"mustNotContain": "<html",
|
||||
"mustContain": "catch-all"
|
||||
},
|
||||
{
|
||||
"path": "/index",
|
||||
"status": 200,
|
||||
"mustContain": "html"
|
||||
},
|
||||
{
|
||||
"path": "/index",
|
||||
"status": 200,
|
||||
"mustContain": "catch-all"
|
||||
},
|
||||
{
|
||||
"path": "/index",
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"RSC": 1
|
||||
},
|
||||
"mustNotContain": "<html",
|
||||
"mustContain": "catch-all"
|
||||
},
|
||||
{
|
||||
"path": "/nested",
|
||||
"status": 200,
|
||||
"mustContain": "html"
|
||||
},
|
||||
{
|
||||
"path": "/nested",
|
||||
"status": 200,
|
||||
"mustContain": "catch-all"
|
||||
},
|
||||
{
|
||||
"path": "/nested",
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"RSC": 1
|
||||
},
|
||||
"mustNotContain": "<html",
|
||||
"mustContain": "catch-all"
|
||||
},
|
||||
{
|
||||
"path": "/nested/index",
|
||||
"status": 200,
|
||||
"mustContain": "html"
|
||||
},
|
||||
{
|
||||
"path": "/nested/index",
|
||||
"status": 200,
|
||||
"mustContain": "catch-all"
|
||||
},
|
||||
{
|
||||
"path": "/nested/index",
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"RSC": 1
|
||||
},
|
||||
"mustNotContain": "<html",
|
||||
"mustContain": "catch-all"
|
||||
},
|
||||
{
|
||||
"path": "/first",
|
||||
"status": 200,
|
||||
|
||||
@@ -7,8 +7,9 @@ import {
|
||||
getImagesConfig,
|
||||
getNextConfig,
|
||||
getServerlessPages,
|
||||
normalizePrefetches,
|
||||
} from '../../src/utils';
|
||||
import { FileRef } from '@vercel/build-utils';
|
||||
import { FileFsRef, FileRef } from '@vercel/build-utils';
|
||||
import { genDir } from '../utils';
|
||||
|
||||
describe('getNextConfig', () => {
|
||||
@@ -408,3 +409,27 @@ describe('getServerlessPages', () => {
|
||||
expect(Object.keys(appPaths)).toEqual(['favicon.ico.js', 'index.js']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizePrefetches', () => {
|
||||
it('should properly prefix prefetches with `__`', async () => {
|
||||
const dummyFile = new FileFsRef({ fsPath: __dirname });
|
||||
|
||||
const appRscPrefetches = {
|
||||
'index.prefetch.rsc': dummyFile,
|
||||
'index/index.prefetch.rsc': dummyFile,
|
||||
'foo.prefetch.rsc': dummyFile,
|
||||
'foo/index.prefetch.rsc': dummyFile,
|
||||
'foo/bar/baz.prefetch.rsc': dummyFile,
|
||||
};
|
||||
|
||||
const updatedPrefetches = normalizePrefetches(appRscPrefetches);
|
||||
|
||||
expect(Object.keys(updatedPrefetches)).toEqual([
|
||||
'__index.prefetch.rsc',
|
||||
'index/__index.prefetch.rsc',
|
||||
'__foo.prefetch.rsc',
|
||||
'foo/__index.prefetch.rsc',
|
||||
'foo/bar/__baz.prefetch.rsc',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user