From 44223268651f1bbd5c6f2b0b315239685dd5716e Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 23 Aug 2023 22:26:58 -0700 Subject: [PATCH] Add handling to leverage RSC prefetch outputs (#10390) Implements handling for the RSC prefetch outputs when available that were added in https://github.com/vercel/next.js/pull/54403 --- .changeset/slimy-comics-serve.md | 5 + packages/next/src/server-build.ts | 103 +++++++++++++++++- packages/next/src/utils.ts | 1 + .../00-app-dir/app/client-nested/page.js | 2 + .../next/test/fixtures/00-app-dir/vercel.json | 42 +++++++ 5 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 .changeset/slimy-comics-serve.md diff --git a/.changeset/slimy-comics-serve.md b/.changeset/slimy-comics-serve.md new file mode 100644 index 00000000000..a00b17546aa --- /dev/null +++ b/.changeset/slimy-comics-serve.md @@ -0,0 +1,5 @@ +--- +"@vercel/next": patch +--- + +Add handling to leverage RSC prefetch outputs diff --git a/packages/next/src/server-build.ts b/packages/next/src/server-build.ts index f2be81087d3..a45ac329bcd 100644 --- a/packages/next/src/server-build.ts +++ b/packages/next/src/server-build.ts @@ -168,12 +168,15 @@ export async function serverBuild({ } } + const APP_PREFETCH_SUFFIX = '.prefetch.rsc'; + let appRscPrefetches: UnwrapPromise> = {}; let appBuildTraces: UnwrapPromise> = {}; let appDir: string | null = null; if (appPathRoutesManifest) { appDir = path.join(pagesDir, '../app'); appBuildTraces = await glob('**/*.js.nft.json', appDir); + appRscPrefetches = await glob(`**/*${APP_PREFETCH_SUFFIX}`, appDir); } const isCorrectNotFoundRoutes = semver.gte( @@ -1225,6 +1228,7 @@ export async function serverBuild({ } const rscHeader = routesManifest.rsc?.header?.toLowerCase() || '__rsc__'; + const rscPrefetchHeader = routesManifest.rsc?.prefetchHeader?.toLowerCase(); const rscVaryHeader = routesManifest?.rsc?.varyHeader || 'RSC, Next-Router-State-Tree, Next-Router-Prefetch'; @@ -1236,6 +1240,7 @@ export async function serverBuild({ output: { ...publicDirectoryFiles, ...lambdas, + ...appRscPrefetches, // Prerenders may override Lambdas -- this is an intentional behavior. ...prerenders, ...staticPages, @@ -1475,6 +1480,49 @@ export async function serverBuild({ ...(appDir ? [ + ...(rscPrefetchHeader + ? [ + { + src: `^${path.posix.join('/', entryDirectory, '/')}`, + has: [ + { + type: 'header', + key: rscPrefetchHeader, + }, + ], + dest: path.posix.join( + '/', + entryDirectory, + '/index.prefetch.rsc' + ), + headers: { vary: rscVaryHeader }, + continue: true, + override: true, + }, + { + src: `^${path.posix.join( + '/', + entryDirectory, + '/((?!.+\\.rsc).+?)(?:/)?$' + )}`, + has: [ + { + type: 'header', + key: rscPrefetchHeader, + }, + ], + dest: path.posix.join( + '/', + entryDirectory, + `/$1${APP_PREFETCH_SUFFIX}` + ), + headers: { vary: rscVaryHeader }, + continue: true, + override: true, + }, + ] + : []), + { src: `^${path.posix.join('/', entryDirectory, '/')}`, has: [ @@ -1539,6 +1587,43 @@ export async function serverBuild({ ] : []), + ...(rscPrefetchHeader + ? [ + { + src: path.posix.join( + '/', + entryDirectory, + `/index${APP_PREFETCH_SUFFIX}` + ), + dest: path.posix.join('/', entryDirectory, '/index.rsc'), + has: [ + { + type: 'header', + key: rscPrefetchHeader, + }, + ], + continue: true, + override: true, + }, + { + src: `^${path.posix.join( + '/', + entryDirectory, + `/(.+?)${APP_PREFETCH_SUFFIX}(?:/)?$` + )}`, + dest: path.posix.join('/', entryDirectory, '/$1.rsc'), + has: [ + { + type: 'header', + key: rscPrefetchHeader, + }, + ], + continue: true, + override: true, + }, + ] + : []), + ...(appDir ? [ // check routes that end in `.rsc` to see if a page with the resulting name (sans-.rsc) exists in the filesystem @@ -1552,8 +1637,18 @@ export async function serverBuild({ key: rscHeader, }, ], + ...(rscPrefetchHeader + ? { + missing: [ + { + type: 'header', + key: rscPrefetchHeader, + }, + ], + } + : {}), check: true, - }, + } as Route, ] : []), @@ -1565,7 +1660,11 @@ export async function serverBuild({ ? [ // rewrite route back to `.rsc`, but skip checking fs { - src: `^${path.posix.join('/', entryDirectory, '/(.*)$')}`, + src: `^${path.posix.join( + '/', + entryDirectory, + '/((?!.+\\.rsc).+?)(?:/)?$' + )}`, has: [ { type: 'header', diff --git a/packages/next/src/utils.ts b/packages/next/src/utils.ts index 990148996b5..a3bd879b3bd 100644 --- a/packages/next/src/utils.ts +++ b/packages/next/src/utils.ts @@ -236,6 +236,7 @@ type RoutesManifestOld = { rsc?: { header: string; varyHeader: string; + prefetchHeader?: string; contentTypeHeader: string; }; skipMiddlewareUrlNormalize?: boolean; diff --git a/packages/next/test/fixtures/00-app-dir/app/client-nested/page.js b/packages/next/test/fixtures/00-app-dir/app/client-nested/page.js index 5b19edbe0cd..1349081c08b 100644 --- a/packages/next/test/fixtures/00-app-dir/app/client-nested/page.js +++ b/packages/next/test/fixtures/00-app-dir/app/client-nested/page.js @@ -1,3 +1,5 @@ +export const dynamic = 'force-dynamic' + export default function ClientPage() { return ( <> diff --git a/packages/next/test/fixtures/00-app-dir/vercel.json b/packages/next/test/fixtures/00-app-dir/vercel.json index c6367d6c79b..1104aa3a420 100644 --- a/packages/next/test/fixtures/00-app-dir/vercel.json +++ b/packages/next/test/fixtures/00-app-dir/vercel.json @@ -37,6 +37,16 @@ "mustContain": ":{", "mustNotContain": "