From 70997533978c9eb4cf02092fa074796ffece27f7 Mon Sep 17 00:00:00 2001 From: Zack Tanner <1939140+ztanner@users.noreply.github.com> Date: Tue, 21 Jan 2025 19:40:55 -0800 Subject: [PATCH] backport: fix prerender issue with intercepting routes + generateStaticParams (#75170) Backports the testcase added in https://github.com/vercel/next.js/pull/75167 in [9bf61bc](https://github.com/vercel/next.js/pull/75170/commits/9bf61bc76c25f3eb345aac7db34929ecde232569). Confirmed failure via [this run](https://github.com/vercel/next.js/actions/runs/12898849331/job/35966678688?pr=75170) Adds the fix in [0ab1e32](https://github.com/vercel/next.js/pull/75170/commits/0ab1e32f04f09de2b2651b7e889633e43f4834a2). This change is identical to the code used in canary, which was added as part of the `rootParams` feature via https://github.com/vercel/next.js/pull/73816 ([ref](https://github.com/vercel/next.js/blob/d12e2e82b778eef8393f47944a258a55c6c508fe/packages/next/src/build/static-paths/app.ts#L316-L317)) This regression was caused by `segments` containing all possible segments (including parallel routes), not just the page segment. As a result, `paramKeys` was incorrectly being calculated. We don't need to traverse the segments to determine the param keys: we have the route regex & matcher, it's more reliable to extract it from that. --- packages/next/src/build/utils.ts | 16 +++------------- .../app/@modal/[...catchAll]/page.tsx | 3 +++ .../generate-static-params/(.)[slug]/page.tsx | 3 +++ .../generate-static-params/[slug]/page.tsx | 19 +++++++++++++++++++ .../interception-dynamic-segment.test.ts | 11 ++++++++++- 5 files changed, 38 insertions(+), 14 deletions(-) create mode 100644 test/e2e/app-dir/interception-dynamic-segment/app/@modal/[...catchAll]/page.tsx create mode 100644 test/e2e/app-dir/interception-dynamic-segment/app/@modal/generate-static-params/(.)[slug]/page.tsx create mode 100644 test/e2e/app-dir/interception-dynamic-segment/app/generate-static-params/[slug]/page.tsx diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts index ff6961d176315..1c4f1a915ff1a 100644 --- a/packages/next/src/build/utils.ts +++ b/packages/next/src/build/utils.ts @@ -1289,18 +1289,8 @@ export async function buildAppStaticPaths({ minimalMode: ciEnvironment.hasNextSupport, }) - const paramKeys = new Set() - - const staticParamKeys = new Set() - for (const segment of segments) { - if (segment.param) { - paramKeys.add(segment.param) - - if (segment.config?.dynamicParams === false) { - staticParamKeys.add(segment.param) - } - } - } + const regex = getRouteRegex(page) + const paramKeys = Object.keys(getRouteMatcher(regex)(page) || {}) const afterRunner = new AfterRunner() @@ -1417,7 +1407,7 @@ export async function buildAppStaticPaths({ // Determine if all the segments have had their parameters provided. If there // was no dynamic parameters, then we've collected all the params. const hadAllParamsGenerated = - paramKeys.size === 0 || + paramKeys.length === 0 || (routeParams.length > 0 && routeParams.every((params) => { for (const key of paramKeys) { diff --git a/test/e2e/app-dir/interception-dynamic-segment/app/@modal/[...catchAll]/page.tsx b/test/e2e/app-dir/interception-dynamic-segment/app/@modal/[...catchAll]/page.tsx new file mode 100644 index 0000000000000..3814ec26adf9f --- /dev/null +++ b/test/e2e/app-dir/interception-dynamic-segment/app/@modal/[...catchAll]/page.tsx @@ -0,0 +1,3 @@ +export default function CatchAll() { + return null +} diff --git a/test/e2e/app-dir/interception-dynamic-segment/app/@modal/generate-static-params/(.)[slug]/page.tsx b/test/e2e/app-dir/interception-dynamic-segment/app/@modal/generate-static-params/(.)[slug]/page.tsx new file mode 100644 index 0000000000000..7c48f6584136b --- /dev/null +++ b/test/e2e/app-dir/interception-dynamic-segment/app/@modal/generate-static-params/(.)[slug]/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return 'intercepted' +} diff --git a/test/e2e/app-dir/interception-dynamic-segment/app/generate-static-params/[slug]/page.tsx b/test/e2e/app-dir/interception-dynamic-segment/app/generate-static-params/[slug]/page.tsx new file mode 100644 index 0000000000000..1d3d6c0b80d7a --- /dev/null +++ b/test/e2e/app-dir/interception-dynamic-segment/app/generate-static-params/[slug]/page.tsx @@ -0,0 +1,19 @@ +type Props = { + params: Promise<{ slug: string }> +} + +export default async function Page({ params }: Props) { + const { slug } = await params + return
Hello {slug}
+} + +export function generateStaticParams() { + return [ + { slug: 'a' }, + { slug: 'b' }, + { slug: 'c' }, + { slug: 'd' }, + { slug: 'e' }, + { slug: 'f' }, + ] +} diff --git a/test/e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts b/test/e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts index 63555583a04b4..84714bb05ca34 100644 --- a/test/e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts +++ b/test/e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts @@ -2,7 +2,7 @@ import { nextTestSetup } from 'e2e-utils' import { check } from 'next-test-utils' describe('interception-dynamic-segment', () => { - const { next } = nextTestSetup({ + const { next, isNextStart } = nextTestSetup({ files: __dirname, }) @@ -15,4 +15,13 @@ describe('interception-dynamic-segment', () => { await check(() => browser.elementById('modal').text(), '') await check(() => browser.elementById('children').text(), /not intercepted/) }) + + if (isNextStart) { + it('should correctly prerender segments with generateStaticParams', async () => { + expect(next.cliOutput).toContain('/generate-static-params/a') + const res = await next.fetch('/generate-static-params/a') + expect(res.status).toBe(200) + expect(res.headers.get('x-nextjs-cache')).toBe('HIT') + }) + } })