Skip to content

Commit

Permalink
[next] fix re-mapping logic for index prefetches (#10750)
Browse files Browse the repository at this point in the history
Follow-up to #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
  • Loading branch information
ztanner committed Oct 24, 2023
1 parent 57541e2 commit fc90a3d
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 17 deletions.
5 changes: 5 additions & 0 deletions .changeset/dry-plants-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@vercel/next": patch
---

fix re-mapping logic for index prefetches
13 changes: 4 additions & 9 deletions packages/next/src/server-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
VariantsManifest,
RSC_CONTENT_TYPE,
RSC_PREFETCH_SUFFIX,
normalizePrefetches,
} from './utils';
import {
nodeFileTrace,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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: [
Expand Down
13 changes: 13 additions & 0 deletions packages/next/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import Link from 'next/link';

export default function Page(props) {
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() {
return [
{
slug: ['']
slug: [''],
},
{
slug: ['index'],
},
{
slug: ['first']
}
]
slug: ['first'],
},
];
}

export const revalidate = 0
export const revalidate = 0;
Original file line number Diff line number Diff line change
@@ -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;
57 changes: 57 additions & 0 deletions packages/next/test/fixtures/00-app-dir-root-catch-all/vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
27 changes: 26 additions & 1 deletion packages/next/test/unit/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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',
]);
});
});

0 comments on commit fc90a3d

Please sign in to comment.