diff --git a/packages/next/src/build/webpack/plugins/next-types-plugin/index.ts b/packages/next/src/build/webpack/plugins/next-types-plugin/index.ts index 17cdbcd0f767f..a192c8f274eba 100644 --- a/packages/next/src/build/webpack/plugins/next-types-plugin/index.ts +++ b/packages/next/src/build/webpack/plugins/next-types-plugin/index.ts @@ -56,6 +56,10 @@ ${ type TEntry = typeof import('${relativePath}.js') +type SegmentParams = T extends Record + ? { [K in keyof T]: T[K] extends string ? string | string[] | undefined : never } + : T + // Check that the entry is a valid entry checkFields }, { __tag__: 'generateStaticParams', __return_type__: ReturnType> }>>() } -type SegmentParams = {[param: string]: string | string[] | undefined} export interface PageProps { params?: Promise searchParams?: Promise diff --git a/test/production/app-dir/next-types-plugin/sync-params-type-check/app/blog/[slug]/page.tsx b/test/production/app-dir/next-types-plugin/sync-params-type-check/app/blog/[slug]/page.tsx new file mode 100644 index 0000000000000..3850641e3d950 --- /dev/null +++ b/test/production/app-dir/next-types-plugin/sync-params-type-check/app/blog/[slug]/page.tsx @@ -0,0 +1,8 @@ +interface Params { + slug: string +} + +export default async function Page({ params }: { params: Promise }) { + const paramsValue = await params + return

slug:{paramsValue.slug}

+} diff --git a/test/production/app-dir/next-types-plugin/sync-params-type-check/app/layout.tsx b/test/production/app-dir/next-types-plugin/sync-params-type-check/app/layout.tsx new file mode 100644 index 0000000000000..888614deda3ba --- /dev/null +++ b/test/production/app-dir/next-types-plugin/sync-params-type-check/app/layout.tsx @@ -0,0 +1,8 @@ +import { ReactNode } from 'react' +export default function Root({ children }: { children: ReactNode }) { + return ( + + {children} + + ) +} diff --git a/test/production/app-dir/next-types-plugin/sync-params-type-check/app/page.tsx b/test/production/app-dir/next-types-plugin/sync-params-type-check/app/page.tsx new file mode 100644 index 0000000000000..ff7159d9149fe --- /dev/null +++ b/test/production/app-dir/next-types-plugin/sync-params-type-check/app/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

hello world

+} diff --git a/test/production/app-dir/next-types-plugin/sync-params-type-check/sync-params-type-check.test.ts b/test/production/app-dir/next-types-plugin/sync-params-type-check/sync-params-type-check.test.ts new file mode 100644 index 0000000000000..ff0b220e3f077 --- /dev/null +++ b/test/production/app-dir/next-types-plugin/sync-params-type-check/sync-params-type-check.test.ts @@ -0,0 +1,35 @@ +import { nextTestSetup } from 'e2e-utils' + +// This next-types-plugin feature only works in webpack +;(process.env.TURBOPACK ? describe.skip : describe)( + 'app-dir - sync-params-type-check', + () => { + const { next } = nextTestSetup({ + files: __dirname, + skipStart: true, + skipDeployment: true, + }) + + it('should pass build with Promise params', async () => { + const { exitCode } = await next.build() + expect(exitCode).toBe(0) + }) + + it('should fail build with sync params', async () => { + await next.patchFile( + 'app/blog/[slug]/page.tsx', + ` + interface Params { slug: string } + export default function Page({ params }: { params: Params }) { + return

slug:{params.slug}

+ } + ` + ) + const { exitCode, cliOutput } = await next.build() + expect(exitCode).toBe(1) + expect(cliOutput).toMatch( + /Type error: Type '{ params: Params; }' does not satisfy the constraint 'PageProps'/ + ) + }) + } +)