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,
|
VariantsManifest,
|
||||||
RSC_CONTENT_TYPE,
|
RSC_CONTENT_TYPE,
|
||||||
RSC_PREFETCH_SUFFIX,
|
RSC_PREFETCH_SUFFIX,
|
||||||
|
normalizePrefetches,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import {
|
import {
|
||||||
nodeFileTrace,
|
nodeFileTrace,
|
||||||
@@ -193,13 +194,7 @@ export async function serverBuild({
|
|||||||
const rscContentTypeHeader =
|
const rscContentTypeHeader =
|
||||||
routesManifest?.rsc?.contentTypeHeader || RSC_CONTENT_TYPE;
|
routesManifest?.rsc?.contentTypeHeader || RSC_CONTENT_TYPE;
|
||||||
|
|
||||||
// index.{ext} outputs get mapped to `/` which we don't want to override
|
appRscPrefetches = normalizePrefetches(appRscPrefetches);
|
||||||
// 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'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure all appRscPrefetches have a contentType since this is used by Next.js
|
// ensure all appRscPrefetches have a contentType since this is used by Next.js
|
||||||
// to determine if it's a valid response
|
// to determine if it's a valid response
|
||||||
@@ -1647,7 +1642,7 @@ export async function serverBuild({
|
|||||||
dest: path.posix.join(
|
dest: path.posix.join(
|
||||||
'/',
|
'/',
|
||||||
entryDirectory,
|
entryDirectory,
|
||||||
`/$1${RSC_PREFETCH_SUFFIX}`
|
`/__$1${RSC_PREFETCH_SUFFIX}`
|
||||||
),
|
),
|
||||||
headers: { vary: rscVaryHeader },
|
headers: { vary: rscVaryHeader },
|
||||||
continue: true,
|
continue: true,
|
||||||
@@ -1742,7 +1737,7 @@ export async function serverBuild({
|
|||||||
src: `^${path.posix.join(
|
src: `^${path.posix.join(
|
||||||
'/',
|
'/',
|
||||||
entryDirectory,
|
entryDirectory,
|
||||||
`/(.+?)${RSC_PREFETCH_SUFFIX}(?:/)?$`
|
`/__(.+?)${RSC_PREFETCH_SUFFIX}(?:/)?$`
|
||||||
)}`,
|
)}`,
|
||||||
dest: path.posix.join('/', entryDirectory, '/$1.rsc'),
|
dest: path.posix.join('/', entryDirectory, '/$1.rsc'),
|
||||||
has: [
|
has: [
|
||||||
|
|||||||
@@ -3120,3 +3120,16 @@ export async function getServerlessPages(params: {
|
|||||||
|
|
||||||
return { pages, appPaths: normalizedAppPaths };
|
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) {
|
export default function Page(props) {
|
||||||
return (
|
return (
|
||||||
<p>catch-all {JSON.stringify(props.params || {})}</p>
|
<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() {
|
export function generateStaticParams() {
|
||||||
return [
|
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",
|
"mustNotContain": "<html",
|
||||||
"mustContain": "catch-all"
|
"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",
|
"path": "/first",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import {
|
|||||||
getImagesConfig,
|
getImagesConfig,
|
||||||
getNextConfig,
|
getNextConfig,
|
||||||
getServerlessPages,
|
getServerlessPages,
|
||||||
|
normalizePrefetches,
|
||||||
} from '../../src/utils';
|
} from '../../src/utils';
|
||||||
import { FileRef } from '@vercel/build-utils';
|
import { FileFsRef, FileRef } from '@vercel/build-utils';
|
||||||
import { genDir } from '../utils';
|
import { genDir } from '../utils';
|
||||||
|
|
||||||
describe('getNextConfig', () => {
|
describe('getNextConfig', () => {
|
||||||
@@ -408,3 +409,27 @@ describe('getServerlessPages', () => {
|
|||||||
expect(Object.keys(appPaths)).toEqual(['favicon.ico.js', 'index.js']);
|
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