From b0b32c2081e38411d235daa695b3af7e03f588fc Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Fri, 26 Apr 2024 16:35:28 -0700 Subject: [PATCH 1/3] Fix missing .rsc outputs for pages prerenders --- packages/next/src/server-build.ts | 58 +++++++++++++------ .../app/nested/[...rest]/page.js | 5 ++ .../nested/blog-fallback-false/[slug].js | 24 ++++++++ .../fixtures/00-app-dir-no-ppr/vercel.json | 44 ++++++++++++++ 4 files changed, 113 insertions(+), 18 deletions(-) create mode 100644 packages/next/test/fixtures/00-app-dir-no-ppr/app/nested/[...rest]/page.js create mode 100644 packages/next/test/fixtures/00-app-dir-no-ppr/pages/nested/blog-fallback-false/[slug].js diff --git a/packages/next/src/server-build.ts b/packages/next/src/server-build.ts index 887f8d7cf68..d761bd9ee98 100644 --- a/packages/next/src/server-build.ts +++ b/packages/next/src/server-build.ts @@ -1648,27 +1648,49 @@ export async function serverBuild({ } } + const output: BuildResult['output'] = { + ...publicDirectoryFiles, + ...lambdas, + ...appRscPrefetches, + ...pagesPlaceholderRscEntries, + // Prerenders may override Lambdas -- this is an intentional behavior. + ...prerenders, + ...staticPages, + ...staticFiles, + ...staticDirectoryFiles, + ...privateOutputs.files, + ...middleware.edgeFunctions, + ...(isNextDataServerResolving + ? { + __next_data_catchall: nextDataCatchallOutput, + } + : {}), + }; + + // we need to ensure all prerenders have a matching .rsc output + // otherwise routing could fall through unexpectedly for the + // fallback: false case as it doesn't have a dynamic route + // to catch the `.rsc` request for app -> pages routing + // TODO: move this inside of onPrerenderRoute instead? + if (appPathRoutesManifest) { + const dummyOutput = new FileBlob({ + data: '{}', + contentType: 'application/json', + }); + + for (const key of Object.keys(prerenders)) { + if (!key.includes('.rsc')) { + if (!output[`${key}.rsc`]) { + output[`${key}.rsc`] = dummyOutput; + } + } + } + } + return { wildcard: wildcardConfig, images: getImagesConfig(imagesManifest), - output: { - ...publicDirectoryFiles, - ...lambdas, - ...appRscPrefetches, - ...pagesPlaceholderRscEntries, - // Prerenders may override Lambdas -- this is an intentional behavior. - ...prerenders, - ...staticPages, - ...staticFiles, - ...staticDirectoryFiles, - ...privateOutputs.files, - ...middleware.edgeFunctions, - ...(isNextDataServerResolving - ? { - __next_data_catchall: nextDataCatchallOutput, - } - : {}), - }, + output, routes: [ /* Desired routes order diff --git a/packages/next/test/fixtures/00-app-dir-no-ppr/app/nested/[...rest]/page.js b/packages/next/test/fixtures/00-app-dir-no-ppr/app/nested/[...rest]/page.js new file mode 100644 index 00000000000..62806a9ce13 --- /dev/null +++ b/packages/next/test/fixtures/00-app-dir-no-ppr/app/nested/[...rest]/page.js @@ -0,0 +1,5 @@ +export default function Page() { + return ( +

nested app router catch-all

+ ) +} diff --git a/packages/next/test/fixtures/00-app-dir-no-ppr/pages/nested/blog-fallback-false/[slug].js b/packages/next/test/fixtures/00-app-dir-no-ppr/pages/nested/blog-fallback-false/[slug].js new file mode 100644 index 00000000000..4d87599e62a --- /dev/null +++ b/packages/next/test/fixtures/00-app-dir-no-ppr/pages/nested/blog-fallback-false/[slug].js @@ -0,0 +1,24 @@ +export default function Page(props) { + return ( + <> +

hello from /nested/blog-fallback-false/[slug]

+ + ); +} + +export function getStaticProps() { + return { + props: { + now: Date.now() + } + } +} + +export function getStaticPaths() { + return { + paths: [ + { params: { slug: 'first' } }, + ], + fallback: false + } +} diff --git a/packages/next/test/fixtures/00-app-dir-no-ppr/vercel.json b/packages/next/test/fixtures/00-app-dir-no-ppr/vercel.json index 57b3a81c0a3..b176f6f786e 100644 --- a/packages/next/test/fixtures/00-app-dir-no-ppr/vercel.json +++ b/packages/next/test/fixtures/00-app-dir-no-ppr/vercel.json @@ -18,6 +18,50 @@ } ], "probes": [ + { + "path": "/nested/blog-fallback-false/first", + "status": 200, + "mustContain": "hello from /nested/blog-fallback-false" + }, + { + "path": "/nested/blog-fallback-false/first", + "status": 200, + "mustContain": "{}", + "mustNotContain": ":", + "responseHeaders": { + "x-matched-path": "/nested/blog-fallback-false/first.rsc" + }, + "headers": { + "RSC": 1, + "Next-Router-Prefetch": 1 + } + }, + { + "path": "/nested/blog-fallback-false/first", + "status": 200, + "mustContain": "{}", + "mustNotContain": ":", + "responseHeaders": { + "x-matched-path": "/nested/blog-fallback-false/first.rsc" + }, + "headers": { + "RSC": 1 + } + }, + { + "path": "/nested/blog-fallback-false/non-existent", + "status": 200, + "mustContain": "nested app router catch-all" + }, + { + "path": "/nested/blog-fallback-false/non-existent", + "status": 200, + "mustContain": ":", + "headers": { + "RSC": 1, + "Next-Router-Prefetch": 1 + } + }, { "path": "/dynamic-index/hello/index", "status": 200, From 335db3625ca53fb523bdf7ddce4b8ffaf94483d1 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Fri, 26 Apr 2024 16:37:21 -0700 Subject: [PATCH 2/3] Create clean-buttons-hunt.md --- .changeset/clean-buttons-hunt.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/clean-buttons-hunt.md diff --git a/.changeset/clean-buttons-hunt.md b/.changeset/clean-buttons-hunt.md new file mode 100644 index 00000000000..8da5b178b4d --- /dev/null +++ b/.changeset/clean-buttons-hunt.md @@ -0,0 +1,5 @@ +--- +"@vercel/next": patch +--- + +Fix missing .rsc outputs for pages prerenders From 44ea1acaef8eeb0e44576aac61ab364e0a2f8ec9 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 29 Apr 2024 18:50:59 -0700 Subject: [PATCH 3/3] move logic inside onPrerenderRoute --- packages/next/src/server-build.ts | 58 ++++++------------- packages/next/src/utils.ts | 16 +++++ .../fixtures/00-app-dir-no-ppr/vercel.json | 2 +- 3 files changed, 35 insertions(+), 41 deletions(-) diff --git a/packages/next/src/server-build.ts b/packages/next/src/server-build.ts index b230af07f68..1a9d470a448 100644 --- a/packages/next/src/server-build.ts +++ b/packages/next/src/server-build.ts @@ -1616,49 +1616,27 @@ export async function serverBuild({ } } - const output: BuildResult['output'] = { - ...publicDirectoryFiles, - ...lambdas, - ...appRscPrefetches, - ...pagesPlaceholderRscEntries, - // Prerenders may override Lambdas -- this is an intentional behavior. - ...prerenders, - ...staticPages, - ...staticFiles, - ...staticDirectoryFiles, - ...privateOutputs.files, - ...middleware.edgeFunctions, - ...(isNextDataServerResolving - ? { - __next_data_catchall: nextDataCatchallOutput, - } - : {}), - }; - - // we need to ensure all prerenders have a matching .rsc output - // otherwise routing could fall through unexpectedly for the - // fallback: false case as it doesn't have a dynamic route - // to catch the `.rsc` request for app -> pages routing - // TODO: move this inside of onPrerenderRoute instead? - if (appPathRoutesManifest) { - const dummyOutput = new FileBlob({ - data: '{}', - contentType: 'application/json', - }); - - for (const key of Object.keys(prerenders)) { - if (!key.includes('.rsc')) { - if (!output[`${key}.rsc`]) { - output[`${key}.rsc`] = dummyOutput; - } - } - } - } - return { wildcard: wildcardConfig, images: getImagesConfig(imagesManifest), - output, + output: { + ...publicDirectoryFiles, + ...lambdas, + ...appRscPrefetches, + ...pagesPlaceholderRscEntries, + // Prerenders may override Lambdas -- this is an intentional behavior. + ...prerenders, + ...staticPages, + ...staticFiles, + ...staticDirectoryFiles, + ...privateOutputs.files, + ...middleware.edgeFunctions, + ...(isNextDataServerResolving + ? { + __next_data_catchall: nextDataCatchallOutput, + } + : {}), + }, routes: [ /* Desired routes order diff --git a/packages/next/src/utils.ts b/packages/next/src/utils.ts index a863aedb849..3f2c3002555 100644 --- a/packages/next/src/utils.ts +++ b/packages/next/src/utils.ts @@ -2463,6 +2463,22 @@ export const onPrerenderRoute = }); } + // we need to ensure all prerenders have a matching .rsc output + // otherwise routing could fall through unexpectedly for the + // fallback: false case as it doesn't have a dynamic route + // to catch the `.rsc` request for app -> pages routing + if (outputPrerenderPathData?.endsWith('.json') && appDir) { + const dummyOutput = new FileBlob({ + data: '{}', + contentType: 'application/json', + }); + const rscKey = `${outputPathPage}.rsc`; + const prefetchRscKey = `${outputPathPage}${RSC_PREFETCH_SUFFIX}`; + + prerenders[rscKey] = dummyOutput; + prerenders[prefetchRscKey] = dummyOutput; + } + ++prerenderGroup; if (routesManifest?.i18n && isBlocking) { diff --git a/packages/next/test/fixtures/00-app-dir-no-ppr/vercel.json b/packages/next/test/fixtures/00-app-dir-no-ppr/vercel.json index b176f6f786e..65806f7fac8 100644 --- a/packages/next/test/fixtures/00-app-dir-no-ppr/vercel.json +++ b/packages/next/test/fixtures/00-app-dir-no-ppr/vercel.json @@ -29,7 +29,7 @@ "mustContain": "{}", "mustNotContain": ":", "responseHeaders": { - "x-matched-path": "/nested/blog-fallback-false/first.rsc" + "x-matched-path": "/nested/blog-fallback-false/first.prefetch.rsc" }, "headers": { "RSC": 1,