Skip to content

Conversation

bgub
Copy link
Contributor

@bgub bgub commented Aug 1, 2025

Generate cache, server, rootParams types on Turbopack

Key changes:

  • Proper handling of root layouts (e.g., [locale]) - file paths and route types now include root params
  • Server types (unstable_rootParams) and cache life types generation maintained from old implementation

Copy link
Contributor Author

bgub commented Aug 1, 2025

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@bgub
Copy link
Contributor Author

bgub commented Aug 1, 2025

@lubieowoce: right now, file paths and route types include root parameters. So you'd for example use PageProps<"/[locale]/blog">. Is this desired behavior?

Still need to add tests (and update old ones) but I've tested locally and core functionality is working!

Comment on lines 1427 to 1375
// Find layouts that could be root layouts (have dynamic segments)
const layoutsWithParams = layoutRoutes
.map(({ route }) => {
const foundParams = Array.from(
route.matchAll(/\[(.*?)\]/g),
(match) => match[1]
)
return { route, params: foundParams }
})
.filter(({ params }) => params.length > 0)

// Sort by path depth (ascending) to find the shallowest layout with params
layoutsWithParams.sort((a, b) => {
const aDepth = a.route.split('/').length
const bDepth = b.route.split('/').length
return aDepth - bDepth
})

// The root layout is the shallowest layout with dynamic segments
if (layoutsWithParams.length > 0) {
const rootLayout = layoutsWithParams[0]
collectedRootParams[rootLayout.route] = rootLayout.params
}
Copy link
Contributor

@vercel vercel bot Aug 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The root layout detection logic has been significantly simplified and may incorrectly identify root layouts, potentially breaking TypeScript definitions for unstable_rootParams().

View Details

Analysis

The refactored root layout detection logic differs substantially from the original implementation in the deleted webpack plugin. The key differences are:

Original logic (deleted plugin):

  • Only considered layouts where ALL path segments (except first and last) were dynamic parameters
  • Used complex logic with allSegmentsAreDynamic check
  • Handled multiple root layouts and marked parameters as optional when appropriate
  • Used isSubpath function to understand layout relationships

New logic (current code):

  • Considers ANY layout with dynamic segments
  • Simply chooses the shallowest layout with any dynamic parameters
  • Always marks all parameters as optional: false
  • No validation of segment structure

This change could lead to incorrect root parameter detection in applications with mixed static and dynamic segments. For example:

  • Layouts: /[tenant]/dashboard/layout.tsx, /[tenant]/[project]/layout.tsx
  • Old logic: Would choose /[tenant]/[project]/layout.tsx (all segments dynamic)
  • New logic: Would choose /[tenant]/dashboard/layout.tsx (shallower path)
  • Result: Wrong root params, potentially breaking unstable_rootParams() types

The original allSegmentsAreDynamic validation appeared to be intentional, ensuring only layouts with fully dynamic paths were considered root layouts.


Recommendation

Restore the allSegmentsAreDynamic validation from the original logic. Add a check to ensure all path segments (excluding first and last) are dynamic parameters before considering a layout as a potential root layout:

const layoutsWithParams = layoutRoutes
  .map(({ route }) => {
    const foundParams = Array.from(
      route.matchAll(/\\[(.*?)\\]/g),
      (match) => match[1]
    )
    
    // Check if all segments (excluding first and last) are dynamic
    const segments = route.split('/').slice(1, -1)
    const allSegmentsAreDynamic = segments.every((segment) => 
      /^\\[[^[.\\]]+\\]$/.test(segment)
    )
    
    return { route, params: foundParams, allSegmentsAreDynamic }
  })
  .filter(({ params, allSegmentsAreDynamic }) => 
    params.length > 0 && allSegmentsAreDynamic
  )

Additionally, consider restoring the multiple root layout detection logic to properly handle the optional parameter flag.

@bgub bgub mentioned this pull request Aug 1, 2025
@bgub bgub changed the title feat: support server, cache types feat: support server, cache, root params types Aug 1, 2025
@bgub bgub force-pushed the feat/type-validation-new branch from f584746 to d12494d Compare August 1, 2025 21:24
@bgub bgub force-pushed the advanced-typegen branch from b9d89c5 to 5c5db37 Compare August 1, 2025 21:24
@bgub bgub force-pushed the feat/type-validation-new branch from d12494d to 7ef0cb7 Compare August 2, 2025 00:33
@bgub bgub force-pushed the advanced-typegen branch 3 times, most recently from b3b5d5a to 13fc670 Compare August 2, 2025 00:56
@ijjk
Copy link
Member

ijjk commented Aug 2, 2025

Failing test suites

Commit: 9c35b1d

__NEXT_EXPERIMENTAL_PPR=true pnpm test test/integration/app-types/app-types.test.js (PPR)

  • app type checking - production mode > should type check invalid entry exports
Expand output

● app type checking - production mode › should type check invalid entry exports

expect(received).toContain(expected) // indexOf

Expected substring: "\"foo\" is not a valid Page export field."
Received string:    "·
[Test Mode] ../../../packages/next
Type error: Type 'typeof import(\"/root/actions-runner/_work/next.js/next.js/test/integration/app-types/src/app/type-checks/config/page\")' does not satisfy the expected type 'AppPageConfig<\"/type-checks/config\">'.
  Types of property 'generateStaticParams' are incompatible.
    Type '(s: string) => Promise<number>' is not assignable to type '(props: { params: {}; }) => any[] | Promise<any[]>'.
      Types of parameters 's' and 'props' are incompatible.
        Type '{ params: {}; }' is not assignable to type 'string'.··
[Test Mode] ../../../packages/next
Type error: Type 'typeof import(\"/root/actions-runner/_work/next.js/next.js/test/integration/app-types/src/app/type-checks/route-handlers/route\")' does not satisfy the expected type 'RouteHandlerConfig<\"/type-checks/route-handlers\">'.
  Types of property 'GET' are incompatible.
    Type '(request: boolean) => void' is not assignable to type '(request: Request, context: { params: Promise<{}>; }) => void | Promise<void> | Response | Promise<Response>'.
      Types of parameters 'request' and 'request' are incompatible.
        Type 'Request' is not assignable to type 'boolean'.··
[Test Mode] ./src/app/type-checks/form/page.tsx:8:13
Type error: Type '\"/wrong-link\"' is not assignable to type '((formData: FormData) => void) | RouteImpl<\"/wrong-link\">'.·
   6 |   const invalidRoutes = (
   7 |     <>
>  8 |       <Form action=\"/wrong-link\"></Form>
     |             ^
   9 |       <Form action=\"/blog/a?1/b\"></Form>
  10 |       <Form action={`/blog/${'a/b/c'}`}></Form>
  11 |     </>·
[Test Mode] ./src/app/type-checks/form/page.tsx:9:13
Type error: Type '\"/blog/a?1/b\"' is not assignable to type '((formData: FormData) => void) | RouteImpl<\"/blog/a?1/b\">'.·
   7 |     <>
   8 |       <Form action=\"/wrong-link\"></Form>
>  9 |       <Form action=\"/blog/a?1/b\"></Form>
     |             ^
  10 |       <Form action={`/blog/${'a/b/c'}`}></Form>
  11 |     </>
  12 |   )·
[Test Mode] ./src/app/type-checks/form/page.tsx:10:13
Type error: Type '\"/blog/a/b/c\"' is not assignable to type '((formData: FormData) => void) | RouteImpl<\"/blog/a/b/c\">'.·
   8 |       <Form action=\"/wrong-link\"></Form>
   9 |       <Form action=\"/blog/a?1/b\"></Form>
> 10 |       <Form action={`/blog/${'a/b/c'}`}></Form>
     |             ^
  11 |     </>
  12 |   )
  13 |·
[Test Mode] ./src/app/type-checks/link/page.tsx:18:13
Type error: Type '\"/(newroot)/dashboard/another\"' is not assignable to type 'URL | Route<\"/(newroot)/dashboard/another\">'.·
  16 |   const shouldFail = (
  17 |     <>
> 18 |       <Card href=\"/(newroot)/dashboard/another\" />
     |             ^
  19 |       <Card href=\"/dashboard\" />
  20 |       <Card href=\"/blog/a/b/c/d\" />
  21 |       <Link href=\"/typing\">test</Link>·
[Test Mode] ./src/app/type-checks/link/page.tsx:19:13
Type error: Type '\"/dashboard\"' is not assignable to type 'URL | Route<\"/dashboard\">'.·
  17 |     <>
  18 |       <Card href=\"/(newroot)/dashboard/another\" />
> 19 |       <Card href=\"/dashboard\" />
     |             ^
  20 |       <Card href=\"/blog/a/b/c/d\" />
  21 |       <Link href=\"/typing\">test</Link>
  22 |       <Link href=\"/button\">test</Link>·
[Test Mode] ./src/app/type-checks/link/page.tsx:20:13
Type error: Type '\"/blog/a/b/c/d\"' is not assignable to type 'URL | Route<\"/blog/a/b/c/d\">'.·
  18 |       <Card href=\"/(newroot)/dashboard/another\" />
  19 |       <Card href=\"/dashboard\" />
> 20 |       <Card href=\"/blog/a/b/c/d\" />
     |             ^
  21 |       <Link href=\"/typing\">test</Link>
  22 |       <Link href=\"/button\">test</Link>
  23 |       <Link href=\"/buttooon\">test</Link>·
[Test Mode] ./src/app/type-checks/link/page.tsx:21:13
Type error: \"/typing\" is not an existing route. If it is intentional, please type it explicitly with `as Route`.·
  19 |       <Card href=\"/dashboard\" />
  20 |       <Card href=\"/blog/a/b/c/d\" />
> 21 |       <Link href=\"/typing\">test</Link>
     |             ^
  22 |       <Link href=\"/button\">test</Link>
  23 |       <Link href=\"/buttooon\">test</Link>
  24 |       <Link href=\"/blog/\">test</Link>·
[Test Mode] ./src/app/type-checks/link/page.tsx:22:13
Type error: \"/button\" is not an existing route. If it is intentional, please type it explicitly with `as Route`.·
  20 |       <Card href=\"/blog/a/b/c/d\" />
  21 |       <Link href=\"/typing\">test</Link>
> 22 |       <Link href=\"/button\">test</Link>
     |             ^
  23 |       <Link href=\"/buttooon\">test</Link>
  24 |       <Link href=\"/blog/\">test</Link>
  25 |       <Link href=\"/blog/a?1/b\">test</Link>·
[Test Mode] ./src/app/type-checks/link/page.tsx:23:13
Type error: \"/buttooon\" is not an existing route. If it is intentional, please type it explicitly with `as Route`.·
  21 |       <Link href=\"/typing\">test</Link>
  22 |       <Link href=\"/button\">test</Link>
> 23 |       <Link href=\"/buttooon\">test</Link>
     |             ^
  24 |       <Link href=\"/blog/\">test</Link>
  25 |       <Link href=\"/blog/a?1/b\">test</Link>
  26 |       <Link href=\"/blog/a#1/b\">test</Link>·
[Test Mode] ./src/app/type-checks/link/page.tsx:24:13
Type error: \"/blog/\" is not an existing route. If it is intentional, please type it explicitly with `as Route`.·
  22 |       <Link href=\"/button\">test</Link>
  23 |       <Link href=\"/buttooon\">test</Link>
> 24 |       <Link href=\"/blog/\">test</Link>
     |             ^
  25 |       <Link href=\"/blog/a?1/b\">test</Link>
  26 |       <Link href=\"/blog/a#1/b\">test</Link>
  27 |       <Link href=\"/blog/v/w/z\">test</Link>·
[Test Mode] ./src/app/type-checks/link/page.tsx:25:13
Type error: \"/blog/a?1/b\" is not an existing route. If it is intentional, please type it explicitly with `as Route`.·
  23 |       <Link href=\"/buttooon\">test</Link>
  24 |       <Link href=\"/blog/\">test</Link>
> 25 |       <Link href=\"/blog/a?1/b\">test</Link>
     |             ^
  26 |       <Link href=\"/blog/a#1/b\">test</Link>
  27 |       <Link href=\"/blog/v/w/z\">test</Link>
  28 |       <Link href=\"/(newroot)/dashboard/another\" />·
[Test Mode] ./src/app/type-checks/link/page.tsx:26:13
Type error: \"/blog/a#1/b\" is not an existing route. If it is intentional, please type it explicitly with `as Route`.·
  24 |       <Link href=\"/blog/\">test</Link>
  25 |       <Link href=\"/blog/a?1/b\">test</Link>
> 26 |       <Link href=\"/blog/a#1/b\">test</Link>
     |             ^
  27 |       <Link href=\"/blog/v/w/z\">test</Link>
  28 |       <Link href=\"/(newroot)/dashboard/another\" />
  29 |       <Link href=\"/dashboard\">test</Link>·
[Test Mode] ./src/app/type-checks/link/page.tsx:27:13
Type error: \"/blog/v/w/z\" is not an existing route. If it is intentional, please type it explicitly with `as Route`.·
  25 |       <Link href=\"/blog/a?1/b\">test</Link>
  26 |       <Link href=\"/blog/a#1/b\">test</Link>
> 27 |       <Link href=\"/blog/v/w/z\">test</Link>
     |             ^
  28 |       <Link href=\"/(newroot)/dashboard/another\" />
  29 |       <Link href=\"/dashboard\">test</Link>
  30 |       <Link href={`/blog/a/${test}`}>test</Link>·
[Test Mode] ./src/app/type-checks/link/page.tsx:28:13
Type error: \"/(newroot)/dashboard/another\" is not an existing route. If it is intentional, please type it explicitly with `as Route`.·
  26 |       <Link href=\"/blog/a#1/b\">test</Link>
  27 |       <Link href=\"/blog/v/w/z\">test</Link>
> 28 |       <Link href=\"/(newroot)/dashboard/another\" />
     |             ^
  29 |       <Link href=\"/dashboard\">test</Link>
  30 |       <Link href={`/blog/a/${test}`}>test</Link>
  31 |       <Link href=\"/rewrite-any\">test</Link>·
[Test Mode] ./src/app/type-checks/link/page.tsx:29:13
Type error: \"/dashboard\" is not an existing route. If it is intentional, please type it explicitly with `as Route`.·
  27 |       <Link href=\"/blog/v/w/z\">test</Link>
  28 |       <Link href=\"/(newroot)/dashboard/another\" />
> 29 |       <Link href=\"/dashboard\">test</Link>
     |             ^
  30 |       <Link href={`/blog/a/${test}`}>test</Link>
  31 |       <Link href=\"/rewrite-any\">test</Link>
  32 |       <Link href=\"/rewrite-one-or-more\">test</Link>·
[Test Mode] ./src/app/type-checks/link/page.tsx:30:13
Type error: \"/blog/a/a/b\" is not an existing route. If it is intentional, please type it explicitly with `as Route`.·
  28 |       <Link href=\"/(newroot)/dashboard/another\" />
  29 |       <Link href=\"/dashboard\">test</Link>
> 30 |       <Link href={`/blog/a/${test}`}>test</Link>
     |             ^
  31 |       <Link href=\"/rewrite-any\">test</Link>
  32 |       <Link href=\"/rewrite-one-or-more\">test</Link>
  33 |       <Link href=\"/rewrite-param/page\">test</Link>·
[Test Mode] ./src/app/type-checks/link/page.tsx:31:13
Type error: \"/rewrite-any\" is not an existing route. Did you mean \"/rewrite\" instead? If it is intentional, please type it explicitly with `as Route`.·
  29 |       <Link href=\"/dashboard\">test</Link>
  30 |       <Link href={`/blog/a/${test}`}>test</Link>
> 31 |       <Link href=\"/rewrite-any\">test</Link>
     |             ^
  32 |       <Link href=\"/rewrite-one-or-more\">test</Link>
  33 |       <Link href=\"/rewrite-param/page\">test</Link>
  34 |       <Link href=\"/rewrite-param/x/page1\">test</Link>·
[Test Mode] ./src/app/type-checks/link/page.tsx:32:13
Type error: \"/rewrite-one-or-more\" is not an existing route. If it is intentional, please type it explicitly with `as Route`.·
  30 |       <Link href={`/blog/a/${test}`}>test</Link>
  31 |       <Link href=\"/rewrite-any\">test</Link>
> 32 |       <Link href=\"/rewrite-one-or-more\">test</Link>
     |             ^
  33 |       <Link href=\"/rewrite-param/page\">test</Link>
  34 |       <Link href=\"/rewrite-param/x/page1\">test</Link>
  35 |       <Link href=\"/redirect/v2/guides/x/page\">test</Link>·
[Test Mode] ./src/app/type-checks/link/page.tsx:33:13
Type error: \"/rewrite-param/page\" is not an existing route. If it is intentional, please type it explicitly with `as Route`.·
  31 |       <Link href=\"/rewrite-any\">test</Link>
  32 |       <Link href=\"/rewrite-one-or-more\">test</Link>
> 33 |       <Link href=\"/rewrite-param/page\">test</Link>
     |             ^
  34 |       <Link href=\"/rewrite-param/x/page1\">test</Link>
  35 |       <Link href=\"/redirect/v2/guides/x/page\">test</Link>
  36 |     </>·
[Test Mode] ./src/app/type-checks/link/page.tsx:34:13
Type error: \"/rewrite-param/x/page1\" is not an existing route. If it is intentional, please type it explicitly with `as Route`.·
  32 |       <Link href=\"/rewrite-one-or-more\">test</Link>
  33 |       <Link href=\"/rewrite-param/page\">test</Link>
> 34 |       <Link href=\"/rewrite-param/x/page1\">test</Link>
     |             ^
  35 |       <Link href=\"/redirect/v2/guides/x/page\">test</Link>
  36 |     </>
  37 |   )·
[Test Mode] ./src/app/type-checks/link/page.tsx:35:13
Type error: \"/redirect/v2/guides/x/page\" is not an existing route. If it is intentional, please type it explicitly with `as Route`.·
  33 |       <Link href=\"/rewrite-param/page\">test</Link>
  34 |       <Link href=\"/rewrite-param/x/page1\">test</Link>
> 35 |       <Link href=\"/redirect/v2/guides/x/page\">test</Link>
     |             ^
  36 |     </>
  37 |   )
  38 |·
[Test Mode] ./src/app/type-checks/router/page.tsx:11:17
Type error: Argument of type '\"/wrong-link\"' is not assignable to parameter of type 'RouteImpl<\"/wrong-link\">'.·
   9 |   function test() {
  10 |     // Invalid routes:
> 11 |     router.push('/wrong-link')
     |                 ^
  12 |     router.push('/blog/a?1/b')
  13 |     router.push(`/blog/${'a/b/c'}`)
  14 |·
[Test Mode] ./src/app/type-checks/router/page.tsx:12:17
Type error: Argument of type '\"/blog/a?1/b\"' is not assignable to parameter of type 'RouteImpl<\"/blog/a?1/b\">'.·
  10 |     // Invalid routes:
  11 |     router.push('/wrong-link')
> 12 |     router.push('/blog/a?1/b')
     |                 ^
  13 |     router.push(`/blog/${'a/b/c'}`)
  14 |
  15 |     // Correctly typed:·
[Test Mode] ./src/app/type-checks/router/page.tsx:13:17
Type error: Argument of type '\"/blog/a/b/c\"' is not assignable to parameter of type 'RouteImpl<\"/blog/a/b/c\">'.·
  11 |     router.push('/wrong-link')
  12 |     router.push('/blog/a?1/b')
> 13 |     router.push(`/blog/${'a/b/c'}`)
     |                 ^
  14 |
  15 |     // Correctly typed:
  16 |     router.push('/dashboard/another')·
"

  75 |     it('should type check invalid entry exports', () => {
  76 |       // Can't export arbitrary things.
> 77 |       expect(errors).toContain(`"foo" is not a valid Page export field.`)
     |                      ^
  78 |
  79 |       // Can't export invalid fields.
  80 |       expect(errors).toMatch(

  at Object.toContain (integration/app-types/app-types.test.js:77:22)

Read more about building and testing Next.js in contributing.md.

pnpm test-start-turbo test/e2e/app-dir/catchall-parallel-routes-group/catchall-parallel-routes-group.test.ts (turbopack)

  • catchall-parallel-routes-group > should work without throwing any errors about invalid pages
Expand output

● catchall-parallel-routes-group › should work without throwing any errors about invalid pages

next build failed with code/signal 1

  107 |             if (code || signal)
  108 |               reject(
> 109 |                 new Error(
      |                 ^
  110 |                   `next build failed with code/signal ${code || signal}`
  111 |                 )
  112 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:109:17)

Read more about building and testing Next.js in contributing.md.

pnpm test-dev-rspack test/e2e/app-dir/app-root-params/generate-static-params.test.ts(rspack)

  • app-root-params - generateStaticParams > should correctly generate types
Expand output

● app-root-params - generateStaticParams › should correctly generate types

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

  41 |   if (!isNextDeploy && !isTurbopack) {
  42 |     it('should correctly generate types', async () => {
> 43 |       expect(await next.hasFile('.next/types/server.d.ts')).toBe(true)
     |                                                             ^
  44 |       const fileContents = await next.readFile('.next/types/server.d.ts')
  45 |       expect(fileContents).toContain(
  46 |         `export function unstable_rootParams(): Promise<{ lang: string, locale: string }>`

  at Object.toBe (e2e/app-dir/app-root-params/generate-static-params.test.ts:43:61)

Read more about building and testing Next.js in contributing.md.

pnpm test-start test/e2e/app-dir/app-root-params/multiple-roots.test.ts

  • app-root-params - multiple roots > should correctly generate types
Expand output

● app-root-params - multiple roots › should correctly generate types

expect(received).toContain(expected) // indexOf

Expected substring: "export function unstable_rootParams(): Promise<{ id?: string }>"
Received string:    "// Type definitions for Next.js server types·
declare module 'next/server' {·
  import type { AsyncLocalStorage as NodeAsyncLocalStorage } from 'async_hooks'
  declare global {
    var AsyncLocalStorage: typeof NodeAsyncLocalStorage
  }
  export { NextFetchEvent } from 'next/dist/server/web/spec-extension/fetch-event'
  export { NextRequest } from 'next/dist/server/web/spec-extension/request'
  export { NextResponse } from 'next/dist/server/web/spec-extension/response'
  export { NextMiddleware, MiddlewareConfig } from 'next/dist/server/web/types'
  export { userAgentFromString } from 'next/dist/server/web/spec-extension/user-agent'
  export { userAgent } from 'next/dist/server/web/spec-extension/user-agent'
  export { URLPattern } from 'next/dist/compiled/@edge-runtime/primitives/url'
  export { ImageResponse } from 'next/dist/server/web/spec-extension/image-response'
  export type { ImageResponseOptions } from 'next/dist/compiled/@vercel/og/types'
  export { after } from 'next/dist/server/after'
  export { connection } from 'next/dist/server/request/connection'
  export type { UnsafeUnwrappedSearchParams } from 'next/dist/server/request/search-params'
  export type { UnsafeUnwrappedParams } from 'next/dist/server/request/params'
  export function unstable_rootParams(): Promise<{ id: string }>
}
"

  26 |       expect(await next.hasFile('.next/types/server.d.ts')).toBe(true)
  27 |       const fileContents = await next.readFile('.next/types/server.d.ts')
> 28 |       expect(fileContents).toContain(
     |                            ^
  29 |         `export function unstable_rootParams(): Promise<{ id?: string }>`
  30 |       )
  31 |     })

  at Object.toContain (e2e/app-dir/app-root-params/multiple-roots.test.ts:28:28)

Read more about building and testing Next.js in contributing.md.

pnpm test-dev-rspack test/e2e/app-dir/app-root-params/simple.test.ts(rspack)

  • app-root-params - simple > should correctly generate types
Expand output

● app-root-params - simple › should correctly generate types

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

  32 |   if (!isNextDeploy && !isTurbopack) {
  33 |     it('should correctly generate types', async () => {
> 34 |       expect(await next.hasFile('.next/types/server.d.ts')).toBe(true)
     |                                                             ^
  35 |       const fileContents = await next.readFile('.next/types/server.d.ts')
  36 |       expect(fileContents).toContain(
  37 |         `export function unstable_rootParams(): Promise<{ lang: string, locale: string }>`

  at Object.toBe (e2e/app-dir/app-root-params/simple.test.ts:34:61)

Read more about building and testing Next.js in contributing.md.

pnpm test-start test/production/app-dir/next-types-plugin/private-folder-convention/index.test.ts

  • next-types-plugin private-folder-convention > should have type for root page
  • next-types-plugin private-folder-convention > should have type for root layout
  • next-types-plugin private-folder-convention > should have type for nested page
  • next-types-plugin private-folder-convention > should have type for nested layout
Expand output

● next-types-plugin private-folder-convention › should have type for root page

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

  12 |     it('should have type for root page', async () => {
  13 |       expect(await next.hasFile('app/page.tsx')).toBe(true)
> 14 |       expect(await next.hasFile('.next/types/app/page.ts')).toBe(true)
     |                                                             ^
  15 |     })
  16 |     it('should have type for root layout', async () => {
  17 |       expect(await next.hasFile('app/layout.tsx')).toBe(true)

  at Object.toBe (production/app-dir/next-types-plugin/private-folder-convention/index.test.ts:14:61)

● next-types-plugin private-folder-convention › should have type for root layout

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

  16 |     it('should have type for root layout', async () => {
  17 |       expect(await next.hasFile('app/layout.tsx')).toBe(true)
> 18 |       expect(await next.hasFile('.next/types/app/layout.ts')).toBe(true)
     |                                                               ^
  19 |     })
  20 |     it('should have type for nested page', async () => {
  21 |       expect(await next.hasFile('app/nested/page.tsx')).toBe(true)

  at Object.toBe (production/app-dir/next-types-plugin/private-folder-convention/index.test.ts:18:63)

● next-types-plugin private-folder-convention › should have type for nested page

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

  20 |     it('should have type for nested page', async () => {
  21 |       expect(await next.hasFile('app/nested/page.tsx')).toBe(true)
> 22 |       expect(await next.hasFile('.next/types/app/nested/page.ts')).toBe(true)
     |                                                                    ^
  23 |     })
  24 |     it('should have type for nested layout', async () => {
  25 |       expect(await next.hasFile('app/nested/layout.tsx')).toBe(true)

  at Object.toBe (production/app-dir/next-types-plugin/private-folder-convention/index.test.ts:22:68)

● next-types-plugin private-folder-convention › should have type for nested layout

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

  24 |     it('should have type for nested layout', async () => {
  25 |       expect(await next.hasFile('app/nested/layout.tsx')).toBe(true)
> 26 |       expect(await next.hasFile('.next/types/app/nested/layout.ts')).toBe(true)
     |                                                                      ^
  27 |     })
  28 |
  29 |     it('should not have type for root page in private folder', async () => {

  at Object.toBe (production/app-dir/next-types-plugin/private-folder-convention/index.test.ts:26:70)

Read more about building and testing Next.js in contributing.md.

pnpm test-start test/production/app-dir/next-types-plugin/sync-params-type-check/sync-params-type-check.test.ts

  • app-dir - sync-params-type-check > should fail build with sync params
Expand output

● app-dir - sync-params-type-check › should fail build with sync params

expect(received).toBe(expected) // Object.is equality

Expected: 1
Received: 0

  27 |       )
  28 |       const { exitCode, cliOutput } = await next.build()
> 29 |       expect(exitCode).toBe(1)
     |                        ^
  30 |       expect(cliOutput).toMatch(
  31 |         /Type error: Type '{ params: Params; }' does not satisfy the constraint 'PageProps'/
  32 |       )

  at Object.toBe (production/app-dir/next-types-plugin/sync-params-type-check/sync-params-type-check.test.ts:29:24)

Read more about building and testing Next.js in contributing.md.

Comment on lines +233 to +442
export function unstable_rootParams(): Promise<{ ${rootParams
.map(
({ param, optional }) =>
// ensure params with dashes are valid keys
`${param.includes('-') ? `'${param}'` : param}${optional ? '?' : ''}: string`
)
.join(', ')} }>
Copy link
Contributor

@vercel vercel bot Aug 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generateServerTypesFile function generates invalid TypeScript syntax when called with an empty rootParams array, creating a malformed function signature.

View Details

Analysis

In the generateServerTypesFile function, the template literal directly embeds the result of rootParams.map(...).join(', ') without checking if the array is empty. If rootParams is an empty array, this will produce:

export function unstable_rootParams(): Promise<{  }>

This creates an empty object type with extra whitespace, which while technically valid TypeScript, is inconsistent with the pattern used in other generation functions (like generateRouteTypesFile) that explicitly handle empty arrays by falling back to appropriate types like never.

While the current caller writeServerTypesFile protects against this by only calling the function when allRootParams.length > 0, the function itself is fragile and could cause issues if called from elsewhere with an empty array. The function should be defensive and handle the empty case gracefully.


Recommendation

Add a check for empty arrays in the generateServerTypesFile function, similar to the pattern used in generateRouteTypesFile. Modify the function to handle the empty case by either returning an appropriate default or by restructuring the template to handle empty parameter lists properly:

export function generateServerTypesFile(
  rootParams: { param: string; optional: boolean }[]
): string {
  const paramsString = rootParams.length > 0 
    ? rootParams
        .map(
          ({ param, optional }) =>
            `${param.includes('-') ? `'${param}'` : param}${optional ? '?' : ''}: string`
        )
        .join(', ')
    : '';
    
  return `// Type definitions for Next.js server types

declare module 'next/server' {
  // ... other exports
  export function unstable_rootParams(): Promise<{ ${paramsString} }>
}
`;
}

@ijjk
Copy link
Member

ijjk commented Aug 2, 2025

Stats from current PR

Default Build (Increase detected ⚠️)
General Overall increase ⚠️
vercel/next.js canary vercel/next.js advanced-typegen Change
buildDuration 22.2s 19.3s N/A
buildDurationCached 18.4s 16.2s N/A
nodeModulesSize 446 MB 446 MB ⚠️ +284 kB
nextStartRea..uration (ms) 457ms 478ms N/A
Client Bundles (main, webpack)
vercel/next.js canary vercel/next.js advanced-typegen Change
234bef07-HASH.js gzip 54.2 kB 54.2 kB N/A
5194.HASH.js gzip 169 B 169 B
8863-HASH.js gzip 5.27 kB 5.27 kB N/A
9304-HASH.js gzip 46 kB 45.3 kB N/A
framework-HASH.js gzip 57.7 kB 57.7 kB N/A
main-app-HASH.js gzip 253 B 254 B N/A
main-HASH.js gzip 36.7 kB 36.5 kB N/A
webpack-HASH.js gzip 1.71 kB 1.71 kB N/A
Overall change 169 B 169 B
Legacy Client Bundles (polyfills)
vercel/next.js canary vercel/next.js advanced-typegen Change
polyfills-HASH.js gzip 39.4 kB 39.4 kB
Overall change 39.4 kB 39.4 kB
Client Pages
vercel/next.js canary vercel/next.js advanced-typegen Change
_app-HASH.js gzip 194 B 193 B N/A
_error-HASH.js gzip 182 B 182 B
amp-HASH.js gzip 502 B 507 B N/A
css-HASH.js gzip 335 B 333 B N/A
dynamic-HASH.js gzip 1.83 kB 1.83 kB N/A
edge-ssr-HASH.js gzip 255 B 255 B
head-HASH.js gzip 350 B 352 B N/A
hooks-HASH.js gzip 385 B 383 B N/A
image-HASH.js gzip 4.65 kB 4.66 kB N/A
index-HASH.js gzip 257 B 259 B N/A
link-HASH.js gzip 2.52 kB 2.52 kB N/A
routerDirect..HASH.js gzip 320 B 318 B N/A
script-HASH.js gzip 387 B 386 B N/A
withRouter-HASH.js gzip 315 B 313 B N/A
1afbb74e6ecf..834.css gzip 106 B 106 B
Overall change 543 B 543 B
Client Build Manifests
vercel/next.js canary vercel/next.js advanced-typegen Change
_buildManifest.js gzip 753 B 751 B N/A
Overall change 0 B 0 B
Rendered Page Sizes
vercel/next.js canary vercel/next.js advanced-typegen Change
index.html gzip 524 B 523 B N/A
link.html gzip 537 B 537 B
withRouter.html gzip 519 B 519 B
Overall change 1.06 kB 1.06 kB
Edge SSR bundle Size
vercel/next.js canary vercel/next.js advanced-typegen Change
edge-ssr.js gzip 120 kB 120 kB N/A
page.js gzip 224 kB 223 kB N/A
Overall change 0 B 0 B
Middleware size
vercel/next.js canary vercel/next.js advanced-typegen Change
middleware-b..fest.js gzip 674 B 675 B N/A
middleware-r..fest.js gzip 156 B 155 B N/A
middleware.js gzip 32.8 kB 32.9 kB N/A
edge-runtime..pack.js gzip 853 B 853 B
Overall change 853 B 853 B
Next Runtimes
vercel/next.js canary vercel/next.js advanced-typegen Change
app-page-exp...dev.js gzip 284 kB 284 kB N/A
app-page-exp..prod.js gzip 156 kB 156 kB
app-page-tur...dev.js gzip 284 kB 284 kB N/A
app-page-tur..prod.js gzip 156 kB 156 kB
app-page-tur...dev.js gzip 272 kB 272 kB
app-page-tur..prod.js gzip 150 kB 150 kB
app-page.run...dev.js gzip 272 kB 272 kB
app-page.run..prod.js gzip 150 kB 150 kB
app-route-ex...dev.js gzip 69.6 kB 69.6 kB
app-route-ex..prod.js gzip 48.8 kB 48.8 kB
app-route-tu...dev.js gzip 69.6 kB 69.6 kB
app-route-tu..prod.js gzip 48.8 kB 48.8 kB
app-route-tu...dev.js gzip 69 kB 69 kB
app-route-tu..prod.js gzip 48.4 kB 48.4 kB
app-route.ru...dev.js gzip 68.9 kB 68.9 kB
app-route.ru..prod.js gzip 48.4 kB 48.4 kB
dist_client_...dev.js gzip 326 B 326 B
dist_client_...dev.js gzip 328 B 328 B
dist_client_...dev.js gzip 320 B 320 B
dist_client_...dev.js gzip 318 B 318 B
pages-api-tu...dev.js gzip 42.3 kB 42.3 kB
pages-api-tu..prod.js gzip 32.5 kB 32.5 kB
pages-api.ru...dev.js gzip 42.2 kB 42.2 kB
pages-api.ru..prod.js gzip 32.5 kB 32.5 kB
pages-turbo....dev.js gzip 52.3 kB 52.3 kB
pages-turbo...prod.js gzip 39.9 kB 39.9 kB
pages.runtim...dev.js gzip 52.4 kB 52.4 kB
pages.runtim..prod.js gzip 40 kB 40 kB
server.runti..prod.js gzip 59.8 kB 59.8 kB
Overall change 2.02 MB 2.02 MB
build cache Overall increase ⚠️
vercel/next.js canary vercel/next.js advanced-typegen Change
0.pack gzip 2.9 MB 2.9 MB N/A
index.pack gzip 93 kB 93.6 kB ⚠️ +560 B
Overall change 93 kB 93.6 kB ⚠️ +560 B
Diff details
Diff for page.js

Diff too large to display

Diff for middleware.js

Diff too large to display

Diff for edge-ssr.js

Diff too large to display

Diff for amp-HASH.js
@@ -1,17 +1,65 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [5034],
   {
-    /***/ 4105: /***/ (
+    /***/ 6212: /***/ (
+      __unused_webpack_module,
+      __webpack_exports__,
+      __webpack_require__
+    ) => {
+      "use strict";
+      __webpack_require__.r(__webpack_exports__);
+      /* harmony export */ __webpack_require__.d(__webpack_exports__, {
+        /* harmony export */ config: () => /* binding */ config,
+        /* harmony export */ default: () => /* binding */ Amp,
+        /* harmony export */
+      });
+      /* harmony import */ var next_amp__WEBPACK_IMPORTED_MODULE_0__ =
+        __webpack_require__(7023);
+      /* harmony import */ var next_amp__WEBPACK_IMPORTED_MODULE_0___default =
+        /*#__PURE__*/ __webpack_require__.n(
+          next_amp__WEBPACK_IMPORTED_MODULE_0__
+        );
+
+      const config = {
+        amp: "hybrid",
+      };
+      function Amp(props) {
+        return (0, next_amp__WEBPACK_IMPORTED_MODULE_0__.useAmp)()
+          ? "AMP mode"
+          : "normal mode";
+      }
+
+      /***/
+    },
+
+    /***/ 7023: /***/ (
       module,
       __unused_webpack_exports,
       __webpack_require__
     ) => {
-      module.exports = __webpack_require__(4642);
+      module.exports = __webpack_require__(9926);
+
+      /***/
+    },
+
+    /***/ 8647: /***/ (
+      __unused_webpack_module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/amp",
+        function () {
+          return __webpack_require__(6212);
+        },
+      ]);
+      if (false) {
+      }
 
       /***/
     },
 
-    /***/ 4642: /***/ (module, exports, __webpack_require__) => {
+    /***/ 9926: /***/ (module, exports, __webpack_require__) => {
       "use strict";
 
       Object.defineProperty(exports, "__esModule", {
@@ -27,8 +75,8 @@
       const _react = /*#__PURE__*/ _interop_require_default._(
         __webpack_require__(5977)
       );
-      const _ampcontextsharedruntime = __webpack_require__(8358);
-      const _ampmode = __webpack_require__(242);
+      const _ampcontextsharedruntime = __webpack_require__(5418);
+      const _ampmode = __webpack_require__(3494);
       function useAmp() {
         // Don't assign the context value to a variable to save bytes
         return (0, _ampmode.isInAmpMode)(
@@ -49,61 +97,13 @@
 
       /***/
     },
-
-    /***/ 5261: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/amp",
-        function () {
-          return __webpack_require__(9550);
-        },
-      ]);
-      if (false) {
-      }
-
-      /***/
-    },
-
-    /***/ 9550: /***/ (
-      __unused_webpack_module,
-      __webpack_exports__,
-      __webpack_require__
-    ) => {
-      "use strict";
-      __webpack_require__.r(__webpack_exports__);
-      /* harmony export */ __webpack_require__.d(__webpack_exports__, {
-        /* harmony export */ config: () => /* binding */ config,
-        /* harmony export */ default: () => /* binding */ Amp,
-        /* harmony export */
-      });
-      /* harmony import */ var next_amp__WEBPACK_IMPORTED_MODULE_0__ =
-        __webpack_require__(4105);
-      /* harmony import */ var next_amp__WEBPACK_IMPORTED_MODULE_0___default =
-        /*#__PURE__*/ __webpack_require__.n(
-          next_amp__WEBPACK_IMPORTED_MODULE_0__
-        );
-
-      const config = {
-        amp: "hybrid",
-      };
-      function Amp(props) {
-        return (0, next_amp__WEBPACK_IMPORTED_MODULE_0__.useAmp)()
-          ? "AMP mode"
-          : "normal mode";
-      }
-
-      /***/
-    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(5261)
+      __webpack_exec__(8647)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for css-HASH.js
@@ -1,7 +1,14 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [9813],
   {
-    /***/ 5267: /***/ (
+    /***/ 1978: /***/ (module) => {
+      // extracted by mini-css-extract-plugin
+      module.exports = { helloWorld: "css_helloWorld__aUdUq" };
+
+      /***/
+    },
+
+    /***/ 6941: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -15,7 +22,7 @@
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(7765);
       /* harmony import */ var _css_module_css__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(6320);
+        __webpack_require__(1978);
       /* harmony import */ var _css_module_css__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           _css_module_css__WEBPACK_IMPORTED_MODULE_1__
@@ -35,14 +42,7 @@
       /***/
     },
 
-    /***/ 6320: /***/ (module) => {
-      // extracted by mini-css-extract-plugin
-      module.exports = { helloWorld: "css_helloWorld__aUdUq" };
-
-      /***/
-    },
-
-    /***/ 9643: /***/ (
+    /***/ 8685: /***/ (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -50,7 +50,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/css",
         function () {
-          return __webpack_require__(5267);
+          return __webpack_require__(6941);
         },
       ]);
       if (false) {
@@ -64,7 +64,7 @@
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(9643)
+      __webpack_exec__(8685)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for dynamic-HASH.js
@@ -1,17 +1,63 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [2291],
   {
-    /***/ 2406: /***/ (
-      module,
-      __unused_webpack_exports,
+    /***/ 2839: /***/ (
+      __unused_webpack_module,
+      __webpack_exports__,
       __webpack_require__
     ) => {
-      module.exports = __webpack_require__(8949);
+      "use strict";
+      __webpack_require__.r(__webpack_exports__);
+      /* harmony export */ __webpack_require__.d(__webpack_exports__, {
+        /* harmony export */ __N_SSP: () => /* binding */ __N_SSP,
+        /* harmony export */ default: () => __WEBPACK_DEFAULT_EXPORT__,
+        /* harmony export */
+      });
+      /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
+        __webpack_require__(7765);
+      /* harmony import */ var next_dynamic__WEBPACK_IMPORTED_MODULE_1__ =
+        __webpack_require__(7444);
+      /* harmony import */ var next_dynamic__WEBPACK_IMPORTED_MODULE_1___default =
+        /*#__PURE__*/ __webpack_require__.n(
+          next_dynamic__WEBPACK_IMPORTED_MODULE_1__
+        );
+
+      const DynamicHello = next_dynamic__WEBPACK_IMPORTED_MODULE_1___default()(
+        () =>
+          __webpack_require__
+            .e(/* import() */ 1376)
+            .then(__webpack_require__.bind(__webpack_require__, 1376))
+            .then((mod) => mod.Hello),
+        {
+          loadableGenerated: {
+            webpack: () => [/*require.resolve*/ 1376],
+          },
+        }
+      );
+      const Page = () =>
+        /*#__PURE__*/ (0, react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(
+          react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.Fragment,
+          {
+            children: [
+              /*#__PURE__*/ (0,
+              react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
+                children: "testing next/dynamic size",
+              }),
+              /*#__PURE__*/ (0,
+              react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(
+                DynamicHello,
+                {}
+              ),
+            ],
+          }
+        );
+      var __N_SSP = true;
+      /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = Page;
 
       /***/
     },
 
-    /***/ 4466: /***/ (
+    /***/ 4478: /***/ (
       __unused_webpack_module,
       exports,
       __webpack_require__
@@ -53,7 +99,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       const _react = /*#__PURE__*/ _interop_require_default._(
         __webpack_require__(5977)
       );
-      const _loadablecontextsharedruntime = __webpack_require__(8452);
+      const _loadablecontextsharedruntime = __webpack_require__(5792);
       function resolve(obj) {
         return obj && obj.default ? obj.default : obj;
       }
@@ -288,63 +334,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       /***/
     },
 
-    /***/ 4545: /***/ (
-      __unused_webpack_module,
-      __webpack_exports__,
-      __webpack_require__
-    ) => {
-      "use strict";
-      __webpack_require__.r(__webpack_exports__);
-      /* harmony export */ __webpack_require__.d(__webpack_exports__, {
-        /* harmony export */ __N_SSP: () => /* binding */ __N_SSP,
-        /* harmony export */ default: () => __WEBPACK_DEFAULT_EXPORT__,
-        /* harmony export */
-      });
-      /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
-        __webpack_require__(7765);
-      /* harmony import */ var next_dynamic__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(2406);
-      /* harmony import */ var next_dynamic__WEBPACK_IMPORTED_MODULE_1___default =
-        /*#__PURE__*/ __webpack_require__.n(
-          next_dynamic__WEBPACK_IMPORTED_MODULE_1__
-        );
-
-      const DynamicHello = next_dynamic__WEBPACK_IMPORTED_MODULE_1___default()(
-        () =>
-          __webpack_require__
-            .e(/* import() */ 5194)
-            .then(__webpack_require__.bind(__webpack_require__, 5194))
-            .then((mod) => mod.Hello),
-        {
-          loadableGenerated: {
-            webpack: () => [/*require.resolve*/ 5194],
-          },
-        }
-      );
-      const Page = () =>
-        /*#__PURE__*/ (0, react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(
-          react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.Fragment,
-          {
-            children: [
-              /*#__PURE__*/ (0,
-              react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
-                children: "testing next/dynamic size",
-              }),
-              /*#__PURE__*/ (0,
-              react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(
-                DynamicHello,
-                {}
-              ),
-            ],
-          }
-        );
-      var __N_SSP = true;
-      /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = Page;
-
-      /***/
-    },
-
-    /***/ 8452: /***/ (
+    /***/ 5792: /***/ (
       __unused_webpack_module,
       exports,
       __webpack_require__
@@ -371,24 +361,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
       /***/
     },
 
-    /***/ 8931: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/dynamic",
-        function () {
-          return __webpack_require__(4545);
-        },
-      ]);
-      if (false) {
-      }
-
-      /***/
-    },
-
-    /***/ 8949: /***/ (module, exports, __webpack_require__) => {
+    /***/ 6153: /***/ (module, exports, __webpack_require__) => {
       "use strict";
 
       Object.defineProperty(exports, "__esModule", {
@@ -421,7 +394,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
         __webpack_require__(5977)
       );
       const _loadablesharedruntime = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(4466)
+        __webpack_require__(4478)
       );
       const isServerSide = "object" === "undefined";
       // Normalize loader to return the module as form { default: Component } for `React.lazy`.
@@ -521,13 +494,40 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
 
       /***/
     },
+
+    /***/ 7444: /***/ (
+      module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      module.exports = __webpack_require__(6153);
+
+      /***/
+    },
+
+    /***/ 9805: /***/ (
+      __unused_webpack_module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/dynamic",
+        function () {
+          return __webpack_require__(2839);
+        },
+      ]);
+      if (false) {
+      }
+
+      /***/
+    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(8931)
+      __webpack_exec__(9805)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for edge-ssr-HASH.js
@@ -1,24 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [676],
   {
-    /***/ 4717: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/edge-ssr",
-        function () {
-          return __webpack_require__(7776);
-        },
-      ]);
-      if (false) {
-      }
-
-      /***/
-    },
-
-    /***/ 7776: /***/ (
+    /***/ 170: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -37,13 +20,30 @@
 
       /***/
     },
+
+    /***/ 8079: /***/ (
+      __unused_webpack_module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/edge-ssr",
+        function () {
+          return __webpack_require__(170);
+        },
+      ]);
+      if (false) {
+      }
+
+      /***/
+    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(4717)
+      __webpack_exec__(8079)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for head-HASH.js
@@ -1,34 +1,17 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [5350],
   {
-    /***/ 943: /***/ (
+    /***/ 1177: /***/ (
       module,
       __unused_webpack_exports,
       __webpack_require__
     ) => {
-      module.exports = __webpack_require__(2554);
+      module.exports = __webpack_require__(8366);
 
       /***/
     },
 
-    /***/ 3829: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/head",
-        function () {
-          return __webpack_require__(4662);
-        },
-      ]);
-      if (false) {
-      }
-
-      /***/
-    },
-
-    /***/ 4662: /***/ (
+    /***/ 1992: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -43,7 +26,7 @@
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(7765);
       /* harmony import */ var next_head__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(943);
+        __webpack_require__(1177);
       /* harmony import */ var next_head__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           next_head__WEBPACK_IMPORTED_MODULE_1__
@@ -76,13 +59,30 @@
 
       /***/
     },
+
+    /***/ 8751: /***/ (
+      __unused_webpack_module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/head",
+        function () {
+          return __webpack_require__(1992);
+        },
+      ]);
+      if (false) {
+      }
+
+      /***/
+    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(3829)
+      __webpack_exec__(8751)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for hooks-HASH.js
@@ -1,7 +1,24 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [9804],
   {
-    /***/ 2452: /***/ (
+    /***/ 2227: /***/ (
+      __unused_webpack_module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/hooks",
+        function () {
+          return __webpack_require__(2770);
+        },
+      ]);
+      if (false) {
+      }
+
+      /***/
+    },
+
+    /***/ 2770: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -59,30 +76,13 @@
 
       /***/
     },
-
-    /***/ 6105: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/hooks",
-        function () {
-          return __webpack_require__(2452);
-        },
-      ]);
-      if (false) {
-      }
-
-      /***/
-    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(6105)
+      __webpack_exec__(2227)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for image-HASH.js

Diff too large to display

Diff for link-HASH.js
@@ -1,143 +1,82 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [4672],
   {
-    /***/ 1585: /***/ (module, exports, __webpack_require__) => {
+    /***/ 2346: /***/ (__unused_webpack_module, exports) => {
       "use strict";
 
       Object.defineProperty(exports, "__esModule", {
         value: true,
       });
-      Object.defineProperty(exports, "useIntersection", {
+      Object.defineProperty(exports, "errorOnce", {
         enumerable: true,
         get: function () {
-          return useIntersection;
+          return errorOnce;
         },
       });
-      const _react = __webpack_require__(5977);
-      const _requestidlecallback = __webpack_require__(356);
-      const hasIntersectionObserver =
-        typeof IntersectionObserver === "function";
-      const observers = new Map();
-      const idList = [];
-      function createObserver(options) {
-        const id = {
-          root: options.root || null,
-          margin: options.rootMargin || "",
-        };
-        const existing = idList.find(
-          (obj) => obj.root === id.root && obj.margin === id.margin
-        );
-        let instance;
-        if (existing) {
-          instance = observers.get(existing);
-          if (instance) {
-            return instance;
-          }
-        }
-        const elements = new Map();
-        const observer = new IntersectionObserver((entries) => {
-          entries.forEach((entry) => {
-            const callback = elements.get(entry.target);
-            const isVisible =
-              entry.isIntersecting || entry.intersectionRatio > 0;
-            if (callback && isVisible) {
-              callback(isVisible);
-            }
-          });
-        }, options);
-        instance = {
-          id,
-          observer,
-          elements,
-        };
-        idList.push(id);
-        observers.set(id, instance);
-        return instance;
-      }
-      function observe(element, callback, options) {
-        const { id, observer, elements } = createObserver(options);
-        elements.set(element, callback);
-        observer.observe(element);
-        return function unobserve() {
-          elements.delete(element);
-          observer.unobserve(element);
-          // Destroy observer when there's nothing left to watch:
-          if (elements.size === 0) {
-            observer.disconnect();
-            observers.delete(id);
-            const index = idList.findIndex(
-              (obj) => obj.root === id.root && obj.margin === id.margin
-            );
-            if (index > -1) {
-              idList.splice(index, 1);
-            }
-          }
-        };
-      }
-      function useIntersection(param) {
-        let { rootRef, rootMargin, disabled } = param;
-        const isDisabled = disabled || !hasIntersectionObserver;
-        const [visible, setVisible] = (0, _react.useState)(false);
-        const elementRef = (0, _react.useRef)(null);
-        const setElement = (0, _react.useCallback)((element) => {
-          elementRef.current = element;
-        }, []);
-        (0, _react.useEffect)(() => {
-          if (hasIntersectionObserver) {
-            if (isDisabled || visible) return;
-            const element = elementRef.current;
-            if (element && element.tagName) {
-              const unobserve = observe(
-                element,
-                (isVisible) => isVisible && setVisible(isVisible),
-                {
-                  root: rootRef == null ? void 0 : rootRef.current,
-                  rootMargin,
-                }
-              );
-              return unobserve;
-            }
-          } else {
-            if (!visible) {
-              const idleCallback = (0,
-              _requestidlecallback.requestIdleCallback)(() => setVisible(true));
-              return () =>
-                (0, _requestidlecallback.cancelIdleCallback)(idleCallback);
-            }
-          }
-          // eslint-disable-next-line react-hooks/exhaustive-deps
-        }, [isDisabled, rootMargin, rootRef, visible, elementRef.current]);
-        const resetVisible = (0, _react.useCallback)(() => {
-          setVisible(false);
-        }, []);
-        return [setElement, visible, resetVisible];
-      }
-      if (
-        (typeof exports.default === "function" ||
-          (typeof exports.default === "object" && exports.default !== null)) &&
-        typeof exports.default.__esModule === "undefined"
-      ) {
-        Object.defineProperty(exports.default, "__esModule", {
-          value: true,
-        });
-        Object.assign(exports.default, exports);
-        module.exports = exports.default;
-      } //# sourceMappingURL=use-intersection.js.map
+      let errorOnce = (_) => {};
+      if (false) {
+      } //# sourceMappingURL=error-once.js.map
 
       /***/
     },
 
-    /***/ 2621: /***/ (
+    /***/ 2783: /***/ (
       module,
       __unused_webpack_exports,
       __webpack_require__
     ) => {
-      module.exports = __webpack_require__(5410);
+      module.exports = __webpack_require__(5926);
 
       /***/
     },
 
-    /***/ 5410: /***/ (module, exports, __webpack_require__) => {
+    /***/ 4174: /***/ (
+      __unused_webpack_module,
+      __webpack_exports__,
+      __webpack_require__
+    ) => {
+      "use strict";
+      __webpack_require__.r(__webpack_exports__);
+      /* harmony export */ __webpack_require__.d(__webpack_exports__, {
+        /* harmony export */ __N_SSP: () => /* binding */ __N_SSP,
+        /* harmony export */ default: () => __WEBPACK_DEFAULT_EXPORT__,
+        /* harmony export */
+      });
+      /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
+        __webpack_require__(7765);
+      /* harmony import */ var next_link__WEBPACK_IMPORTED_MODULE_1__ =
+        __webpack_require__(2783);
+      /* harmony import */ var next_link__WEBPACK_IMPORTED_MODULE_1___default =
+        /*#__PURE__*/ __webpack_require__.n(
+          next_link__WEBPACK_IMPORTED_MODULE_1__
+        );
+
+      function aLink(props) {
+        return /*#__PURE__*/ (0,
+        react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
+          children: [
+            /*#__PURE__*/ (0,
+            react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h3", {
+              children: "A Link page!",
+            }),
+            /*#__PURE__*/ (0,
+            react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(
+              next_link__WEBPACK_IMPORTED_MODULE_1___default(),
+              {
+                href: "/",
+                children: "Go to /",
+              }
+            ),
+          ],
+        });
+      }
+      var __N_SSP = true;
+      /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = aLink;
+
+      /***/
+    },
+
+    /***/ 5926: /***/ (module, exports, __webpack_require__) => {
       "use strict";
       /* __next_internal_client_entry_do_not_use__  cjs */
       Object.defineProperty(exports, "__esModule", {
@@ -164,17 +103,17 @@
       const _react = /*#__PURE__*/ _interop_require_wildcard._(
         __webpack_require__(5977)
       );
-      const _resolvehref = __webpack_require__(224);
-      const _islocalurl = __webpack_require__(7746);
-      const _formaturl = __webpack_require__(315);
-      const _utils = __webpack_require__(8709);
-      const _addlocale = __webpack_require__(6358);
-      const _routercontextsharedruntime = __webpack_require__(4095);
-      const _useintersection = __webpack_require__(1585);
-      const _getdomainlocale = __webpack_require__(8802);
-      const _addbasepath = __webpack_require__(6151);
-      const _usemergedref = __webpack_require__(9100);
-      const _erroronce = __webpack_require__(9574);
+      const _resolvehref = __webpack_require__(6708);
+      const _islocalurl = __webpack_require__(6526);
+      const _formaturl = __webpack_require__(5575);
+      const _utils = __webpack_require__(3497);
+      const _addlocale = __webpack_require__(722);
+      const _routercontextsharedruntime = __webpack_require__(1235);
+      const _useintersection = __webpack_require__(8069);
+      const _getdomainlocale = __webpack_require__(9734);
+      const _addbasepath = __webpack_require__(4419);
+      const _usemergedref = __webpack_require__(6136);
+      const _erroronce = __webpack_require__(2346);
       const prefetched = new Set();
       function prefetch(router, href, as, options) {
         if (false) {
@@ -563,43 +502,7 @@
       /***/
     },
 
-    /***/ 8802: /***/ (module, exports, __webpack_require__) => {
-      "use strict";
-
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      Object.defineProperty(exports, "getDomainLocale", {
-        enumerable: true,
-        get: function () {
-          return getDomainLocale;
-        },
-      });
-      const _normalizetrailingslash = __webpack_require__(1652);
-      const basePath =
-        /* unused pure expression or super */ null && (false || "");
-      function getDomainLocale(path, locale, locales, domainLocales) {
-        if (false) {
-        } else {
-          return false;
-        }
-      }
-      if (
-        (typeof exports.default === "function" ||
-          (typeof exports.default === "object" && exports.default !== null)) &&
-        typeof exports.default.__esModule === "undefined"
-      ) {
-        Object.defineProperty(exports.default, "__esModule", {
-          value: true,
-        });
-        Object.assign(exports.default, exports);
-        module.exports = exports.default;
-      } //# sourceMappingURL=get-domain-locale.js.map
-
-      /***/
-    },
-
-    /***/ 9100: /***/ (module, exports, __webpack_require__) => {
+    /***/ 6136: /***/ (module, exports, __webpack_require__) => {
       "use strict";
 
       Object.defineProperty(exports, "__esModule", {
@@ -677,26 +580,7 @@
       /***/
     },
 
-    /***/ 9574: /***/ (__unused_webpack_module, exports) => {
-      "use strict";
-
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      Object.defineProperty(exports, "errorOnce", {
-        enumerable: true,
-        get: function () {
-          return errorOnce;
-        },
-      });
-      let errorOnce = (_) => {};
-      if (false) {
-      } //# sourceMappingURL=error-once.js.map
-
-      /***/
-    },
-
-    /***/ 9693: /***/ (
+    /***/ 7047: /***/ (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -704,7 +588,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/link",
         function () {
-          return __webpack_require__(9948);
+          return __webpack_require__(4174);
         },
       ]);
       if (false) {
@@ -713,48 +597,164 @@
       /***/
     },
 
-    /***/ 9948: /***/ (
-      __unused_webpack_module,
-      __webpack_exports__,
-      __webpack_require__
-    ) => {
+    /***/ 8069: /***/ (module, exports, __webpack_require__) => {
       "use strict";
-      __webpack_require__.r(__webpack_exports__);
-      /* harmony export */ __webpack_require__.d(__webpack_exports__, {
-        /* harmony export */ __N_SSP: () => /* binding */ __N_SSP,
-        /* harmony export */ default: () => __WEBPACK_DEFAULT_EXPORT__,
-        /* harmony export */
+
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
       });
-      /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
-        __webpack_require__(7765);
-      /* harmony import */ var next_link__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(2621);
-      /* harmony import */ var next_link__WEBPACK_IMPORTED_MODULE_1___default =
-        /*#__PURE__*/ __webpack_require__.n(
-          next_link__WEBPACK_IMPORTED_MODULE_1__
+      Object.defineProperty(exports, "useIntersection", {
+        enumerable: true,
+        get: function () {
+          return useIntersection;
+        },
+      });
+      const _react = __webpack_require__(5977);
+      const _requestidlecallback = __webpack_require__(3432);
+      const hasIntersectionObserver =
+        typeof IntersectionObserver === "function";
+      const observers = new Map();
+      const idList = [];
+      function createObserver(options) {
+        const id = {
+          root: options.root || null,
+          margin: options.rootMargin || "",
+        };
+        const existing = idList.find(
+          (obj) => obj.root === id.root && obj.margin === id.margin
         );
-
-      function aLink(props) {
-        return /*#__PURE__*/ (0,
-        react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
-          children: [
-            /*#__PURE__*/ (0,
-            react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h3", {
-              children: "A Link page!",
-            }),
-            /*#__PURE__*/ (0,
-            react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(
-              next_link__WEBPACK_IMPORTED_MODULE_1___default(),
-              {
-                href: "/",
-                children: "Go to /",
-              }
-            ),
-          ],
+        let instance;
+        if (existing) {
+          instance = observers.get(existing);
+          if (instance) {
+            return instance;
+          }
+        }
+        const elements = new Map();
+        const observer = new IntersectionObserver((entries) => {
+          entries.forEach((entry) => {
+            const callback = elements.get(entry.target);
+            const isVisible =
+              entry.isIntersecting || entry.intersectionRatio > 0;
+            if (callback && isVisible) {
+              callback(isVisible);
+            }
+          });
+        }, options);
+        instance = {
+          id,
+          observer,
+          elements,
+        };
+        idList.push(id);
+        observers.set(id, instance);
+        return instance;
+      }
+      function observe(element, callback, options) {
+        const { id, observer, elements } = createObserver(options);
+        elements.set(element, callback);
+        observer.observe(element);
+        return function unobserve() {
+          elements.delete(element);
+          observer.unobserve(element);
+          // Destroy observer when there's nothing left to watch:
+          if (elements.size === 0) {
+            observer.disconnect();
+            observers.delete(id);
+            const index = idList.findIndex(
+              (obj) => obj.root === id.root && obj.margin === id.margin
+            );
+            if (index > -1) {
+              idList.splice(index, 1);
+            }
+          }
+        };
+      }
+      function useIntersection(param) {
+        let { rootRef, rootMargin, disabled } = param;
+        const isDisabled = disabled || !hasIntersectionObserver;
+        const [visible, setVisible] = (0, _react.useState)(false);
+        const elementRef = (0, _react.useRef)(null);
+        const setElement = (0, _react.useCallback)((element) => {
+          elementRef.current = element;
+        }, []);
+        (0, _react.useEffect)(() => {
+          if (hasIntersectionObserver) {
+            if (isDisabled || visible) return;
+            const element = elementRef.current;
+            if (element && element.tagName) {
+              const unobserve = observe(
+                element,
+                (isVisible) => isVisible && setVisible(isVisible),
+                {
+                  root: rootRef == null ? void 0 : rootRef.current,
+                  rootMargin,
+                }
+              );
+              return unobserve;
+            }
+          } else {
+            if (!visible) {
+              const idleCallback = (0,
+              _requestidlecallback.requestIdleCallback)(() => setVisible(true));
+              return () =>
+                (0, _requestidlecallback.cancelIdleCallback)(idleCallback);
+            }
+          }
+          // eslint-disable-next-line react-hooks/exhaustive-deps
+        }, [isDisabled, rootMargin, rootRef, visible, elementRef.current]);
+        const resetVisible = (0, _react.useCallback)(() => {
+          setVisible(false);
+        }, []);
+        return [setElement, visible, resetVisible];
+      }
+      if (
+        (typeof exports.default === "function" ||
+          (typeof exports.default === "object" && exports.default !== null)) &&
+        typeof exports.default.__esModule === "undefined"
+      ) {
+        Object.defineProperty(exports.default, "__esModule", {
+          value: true,
         });
+        Object.assign(exports.default, exports);
+        module.exports = exports.default;
+      } //# sourceMappingURL=use-intersection.js.map
+
+      /***/
+    },
+
+    /***/ 9734: /***/ (module, exports, __webpack_require__) => {
+      "use strict";
+
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
+      });
+      Object.defineProperty(exports, "getDomainLocale", {
+        enumerable: true,
+        get: function () {
+          return getDomainLocale;
+        },
+      });
+      const _normalizetrailingslash = __webpack_require__(504);
+      const basePath =
+        /* unused pure expression or super */ null && (false || "");
+      function getDomainLocale(path, locale, locales, domainLocales) {
+        if (false) {
+        } else {
+          return false;
+        }
       }
-      var __N_SSP = true;
-      /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = aLink;
+      if (
+        (typeof exports.default === "function" ||
+          (typeof exports.default === "object" && exports.default !== null)) &&
+        typeof exports.default.__esModule === "undefined"
+      ) {
+        Object.defineProperty(exports.default, "__esModule", {
+          value: true,
+        });
+        Object.assign(exports.default, exports);
+        module.exports = exports.default;
+      } //# sourceMappingURL=get-domain-locale.js.map
 
       /***/
     },
@@ -764,7 +764,7 @@
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(9693)
+      __webpack_exec__(7047)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for script-HASH.js
@@ -1,7 +1,34 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [1209],
   {
-    /***/ 3699: /***/ (
+    /***/ 1008: /***/ (
+      module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      module.exports = __webpack_require__(2817);
+
+      /***/
+    },
+
+    /***/ 6951: /***/ (
+      __unused_webpack_module,
+      __unused_webpack_exports,
+      __webpack_require__
+    ) => {
+      (window.__NEXT_P = window.__NEXT_P || []).push([
+        "/script",
+        function () {
+          return __webpack_require__(8889);
+        },
+      ]);
+      if (false) {
+      }
+
+      /***/
+    },
+
+    /***/ 8889: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -16,7 +43,7 @@
       /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ =
         __webpack_require__(7765);
       /* harmony import */ var next_script__WEBPACK_IMPORTED_MODULE_1__ =
-        __webpack_require__(4802);
+        __webpack_require__(1008);
       /* harmony import */ var next_script__WEBPACK_IMPORTED_MODULE_1___default =
         /*#__PURE__*/ __webpack_require__.n(
           next_script__WEBPACK_IMPORTED_MODULE_1__
@@ -48,40 +75,13 @@
 
       /***/
     },
-
-    /***/ 4802: /***/ (
-      module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      module.exports = __webpack_require__(8661);
-
-      /***/
-    },
-
-    /***/ 7861: /***/ (
-      __unused_webpack_module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      (window.__NEXT_P = window.__NEXT_P || []).push([
-        "/script",
-        function () {
-          return __webpack_require__(3699);
-        },
-      ]);
-      if (false) {
-      }
-
-      /***/
-    },
   },
   /******/ (__webpack_require__) => {
     // webpackRuntimeModules
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>
-      __webpack_exec__(7861)
+      __webpack_exec__(6951)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for 8863-HASH.js
@@ -1,8 +1,33 @@
 "use strict";
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
-  [8863],
+  [7009],
   {
-    /***/ 22: /***/ (module, exports, __webpack_require__) => {
+    /***/ 414: /***/ (
+      __unused_webpack_module,
+      exports,
+      __webpack_require__
+    ) => {
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
+      });
+      Object.defineProperty(exports, "AmpStateContext", {
+        enumerable: true,
+        get: function () {
+          return AmpStateContext;
+        },
+      });
+      const _interop_require_default = __webpack_require__(2726);
+      const _react = /*#__PURE__*/ _interop_require_default._(
+        __webpack_require__(2224)
+      );
+      const AmpStateContext = _react.default.createContext({});
+      if (false) {
+      } //# sourceMappingURL=amp-context.shared-runtime.js.map
+
+      /***/
+    },
+
+    /***/ 1912: /***/ (module, exports, __webpack_require__) => {
       Object.defineProperty(exports, "__esModule", {
         value: true,
       });
@@ -12,7 +37,7 @@
           return useMergedRef;
         },
       });
-      const _react = __webpack_require__(2786);
+      const _react = __webpack_require__(2224);
       function useMergedRef(refA, refB) {
         const cleanupA = (0, _react.useRef)(null);
         const cleanupB = (0, _react.useRef)(null);
@@ -78,56 +103,99 @@
       /***/
     },
 
-    /***/ 936: /***/ (__unused_webpack_module, exports) => {
+    /***/ 2843: /***/ (
+      __unused_webpack_module,
+      exports,
+      __webpack_require__
+    ) => {
       Object.defineProperty(exports, "__esModule", {
         value: true,
       });
-      0 && 0;
-      function _export(target, all) {
-        for (var name in all)
-          Object.defineProperty(target, name, {
-            enumerable: true,
-            get: all[name],
-          });
-      }
-      _export(exports, {
-        VALID_LOADERS: function () {
-          return VALID_LOADERS;
+      Object.defineProperty(exports, "RouterContext", {
+        enumerable: true,
+        get: function () {
+          return RouterContext;
         },
-        imageConfigDefault: function () {
-          return imageConfigDefault;
+      });
+      const _interop_require_default = __webpack_require__(2726);
+      const _react = /*#__PURE__*/ _interop_require_default._(
+        __webpack_require__(2224)
+      );
+      const RouterContext = _react.default.createContext(null);
+      if (false) {
+      } //# sourceMappingURL=router-context.shared-runtime.js.map
+
+      /***/
+    },
+
+    /***/ 3003: /***/ (__unused_webpack_module, exports) => {
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
+      });
+      Object.defineProperty(exports, "default", {
+        enumerable: true,
+        get: function () {
+          return _default;
         },
       });
-      const VALID_LOADERS = [
-        "default",
-        "imgix",
-        "cloudinary",
-        "akamai",
-        "custom",
-      ];
-      const imageConfigDefault = {
-        deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
-        imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
-        path: "/_next/image",
-        loader: "default",
-        loaderFile: "",
-        domains: [],
-        disableStaticImages: false,
-        minimumCacheTTL: 60,
-        formats: ["image/webp"],
-        dangerouslyAllowSVG: false,
-        contentSecurityPolicy: "script-src 'none'; frame-src 'none'; sandbox;",
-        contentDispositionType: "attachment",
-        localPatterns: undefined,
-        remotePatterns: [],
-        qualities: undefined,
-        unoptimized: false,
-      }; //# sourceMappingURL=image-config.js.map
+      const DEFAULT_Q = 75;
+      function defaultLoader(param) {
+        let { config, src, width, quality } = param;
+        var _config_qualities;
+        if (false) {
+        }
+        const q =
+          quality ||
+          ((_config_qualities = config.qualities) == null
+            ? void 0
+            : _config_qualities.reduce((prev, cur) =>
+                Math.abs(cur - DEFAULT_Q) < Math.abs(prev - DEFAULT_Q)
+                  ? cur
+                  : prev
+              )) ||
+          DEFAULT_Q;
+        return (
+          config.path +
+          "?url=" +
+          encodeURIComponent(src) +
+          "&w=" +
+          width +
+          "&q=" +
+          q +
+          (src.startsWith("/_next/static/media/") && false ? 0 : "")
+        );
+      }
+      // We use this to determine if the import is the default loader
+      // or a custom loader defined by the user in next.config.js
+      defaultLoader.__next_img_default = true;
+      const _default = defaultLoader; //# sourceMappingURL=image-loader.js.map
 
       /***/
     },
 
-    /***/ 1268: /***/ (__unused_webpack_module, exports) => {
+    /***/ 3810: /***/ (__unused_webpack_module, exports) => {
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
+      });
+      Object.defineProperty(exports, "isInAmpMode", {
+        enumerable: true,
+        get: function () {
+          return isInAmpMode;
+        },
+      });
+      function isInAmpMode(param) {
+        let {
+          ampFirst = false,
+          hybrid = false,
+          hasQuery = false,
+        } = param === void 0 ? {} : param;
+        return ampFirst || (hybrid && hasQuery);
+      } //# sourceMappingURL=amp-mode.js.map
+
+      /***/
+    },
+
+    /***/ 5218: /***/ (__unused_webpack_module, exports) => {
       /**
        * A shared function, used on both client and server, to generate a SVG blur placeholder.
        */
@@ -181,7 +249,7 @@
       /***/
     },
 
-    /***/ 1796: /***/ (module, exports, __webpack_require__) => {
+    /***/ 5402: /***/ (module, exports, __webpack_require__) => {
       /* __next_internal_client_entry_do_not_use__  cjs */
       Object.defineProperty(exports, "__esModule", {
         value: true,
@@ -202,19 +270,19 @@
           return defaultHead;
         },
       });
-      const _interop_require_default = __webpack_require__(8182);
-      const _interop_require_wildcard = __webpack_require__(8319);
-      const _jsxruntime = __webpack_require__(1050);
+      const _interop_require_default = __webpack_require__(2726);
+      const _interop_require_wildcard = __webpack_require__(2527);
+      const _jsxruntime = __webpack_require__(8204);
       const _react = /*#__PURE__*/ _interop_require_wildcard._(
-        __webpack_require__(2786)
+        __webpack_require__(2224)
       );
       const _sideeffect = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(5581)
+        __webpack_require__(9319)
       );
-      const _ampcontextsharedruntime = __webpack_require__(3800);
-      const _headmanagercontextsharedruntime = __webpack_require__(4950);
-      const _ampmode = __webpack_require__(7824);
-      const _warnonce = __webpack_require__(2854);
+      const _ampcontextsharedruntime = __webpack_require__(414);
+      const _headmanagercontextsharedruntime = __webpack_require__(3100);
+      const _ampmode = __webpack_require__(3810);
+      const _warnonce = __webpack_require__(4504);
       function defaultHead(inAmpMode) {
         if (inAmpMode === void 0) inAmpMode = false;
         const head = [
@@ -379,7 +447,7 @@
       /***/
     },
 
-    /***/ 2843: /***/ (
+    /***/ 6745: /***/ (
       __unused_webpack_module,
       exports,
       __webpack_require__
@@ -393,9 +461,9 @@
           return getImgProps;
         },
       });
-      const _warnonce = __webpack_require__(2854);
-      const _imageblursvg = __webpack_require__(1268);
-      const _imageconfig = __webpack_require__(936);
+      const _warnonce = __webpack_require__(4504);
+      const _imageblursvg = __webpack_require__(5218);
+      const _imageconfig = __webpack_require__(9278);
       const VALID_LOADING_VALUES =
         /* unused pure expression or super */ null && [
           "lazy",
@@ -822,245 +890,7 @@
       /***/
     },
 
-    /***/ 3800: /***/ (
-      __unused_webpack_module,
-      exports,
-      __webpack_require__
-    ) => {
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      Object.defineProperty(exports, "AmpStateContext", {
-        enumerable: true,
-        get: function () {
-          return AmpStateContext;
-        },
-      });
-      const _interop_require_default = __webpack_require__(8182);
-      const _react = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(2786)
-      );
-      const AmpStateContext = _react.default.createContext({});
-      if (false) {
-      } //# sourceMappingURL=amp-context.shared-runtime.js.map
-
-      /***/
-    },
-
-    /***/ 5581: /***/ (
-      __unused_webpack_module,
-      exports,
-      __webpack_require__
-    ) => {
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      Object.defineProperty(exports, "default", {
-        enumerable: true,
-        get: function () {
-          return SideEffect;
-        },
-      });
-      const _react = __webpack_require__(2786);
-      const isServer = "object" === "undefined";
-      const useClientOnlyLayoutEffect = isServer
-        ? () => {}
-        : _react.useLayoutEffect;
-      const useClientOnlyEffect = isServer ? () => {} : _react.useEffect;
-      function SideEffect(props) {
-        const { headManager, reduceComponentsToState } = props;
-        function emitChange() {
-          if (headManager && headManager.mountedInstances) {
-            const headElements = _react.Children.toArray(
-              Array.from(headManager.mountedInstances).filter(Boolean)
-            );
-            headManager.updateHead(
-              reduceComponentsToState(headElements, props)
-            );
-          }
-        }
-        if (isServer) {
-          var _headManager_mountedInstances;
-          headManager == null
-            ? void 0
-            : (_headManager_mountedInstances = headManager.mountedInstances) ==
-              null
-            ? void 0
-            : _headManager_mountedInstances.add(props.children);
-          emitChange();
-        }
-        useClientOnlyLayoutEffect(() => {
-          var _headManager_mountedInstances;
-          headManager == null
-            ? void 0
-            : (_headManager_mountedInstances = headManager.mountedInstances) ==
-              null
-            ? void 0
-            : _headManager_mountedInstances.add(props.children);
-          return () => {
-            var _headManager_mountedInstances;
-            headManager == null
-              ? void 0
-              : (_headManager_mountedInstances =
-                  headManager.mountedInstances) == null
-              ? void 0
-              : _headManager_mountedInstances.delete(props.children);
-          };
-        });
-        // We need to call `updateHead` method whenever the `SideEffect` is trigger in all
-        // life-cycles: mount, update, unmount. However, if there are multiple `SideEffect`s
-        // being rendered, we only trigger the method from the last one.
-        // This is ensured by keeping the last unflushed `updateHead` in the `_pendingUpdate`
-        // singleton in the layout effect pass, and actually trigger it in the effect pass.
-        useClientOnlyLayoutEffect(() => {
-          if (headManager) {
-            headManager._pendingUpdate = emitChange;
-          }
-          return () => {
-            if (headManager) {
-              headManager._pendingUpdate = emitChange;
-            }
-          };
-        });
-        useClientOnlyEffect(() => {
-          if (headManager && headManager._pendingUpdate) {
-            headManager._pendingUpdate();
-            headManager._pendingUpdate = null;
-          }
-          return () => {
-            if (headManager && headManager._pendingUpdate) {
-              headManager._pendingUpdate();
-              headManager._pendingUpdate = null;
-            }
-          };
-        });
-        return null;
-      } //# sourceMappingURL=side-effect.js.map
-
-      /***/
-    },
-
-    /***/ 7053: /***/ (
-      __unused_webpack_module,
-      exports,
-      __webpack_require__
-    ) => {
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      Object.defineProperty(exports, "RouterContext", {
-        enumerable: true,
-        get: function () {
-          return RouterContext;
-        },
-      });
-      const _interop_require_default = __webpack_require__(8182);
-      const _react = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(2786)
-      );
-      const RouterContext = _react.default.createContext(null);
-      if (false) {
-      } //# sourceMappingURL=router-context.shared-runtime.js.map
-
-      /***/
-    },
-
-    /***/ 7281: /***/ (__unused_webpack_module, exports) => {
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      Object.defineProperty(exports, "default", {
-        enumerable: true,
-        get: function () {
-          return _default;
-        },
-      });
-      const DEFAULT_Q = 75;
-      function defaultLoader(param) {
-        let { config, src, width, quality } = param;
-        var _config_qualities;
-        if (false) {
-        }
-        const q =
-          quality ||
-          ((_config_qualities = config.qualities) == null
-            ? void 0
-            : _config_qualities.reduce((prev, cur) =>
-                Math.abs(cur - DEFAULT_Q) < Math.abs(prev - DEFAULT_Q)
-                  ? cur
-                  : prev
-              )) ||
-          DEFAULT_Q;
-        return (
-          config.path +
-          "?url=" +
-          encodeURIComponent(src) +
-          "&w=" +
-          width +
-          "&q=" +
-          q +
-          (src.startsWith("/_next/static/media/") && false ? 0 : "")
-        );
-      }
-      // We use this to determine if the import is the default loader
-      // or a custom loader defined by the user in next.config.js
-      defaultLoader.__next_img_default = true;
-      const _default = defaultLoader; //# sourceMappingURL=image-loader.js.map
-
-      /***/
-    },
-
-    /***/ 7480: /***/ (
-      __unused_webpack_module,
-      exports,
-      __webpack_require__
-    ) => {
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      Object.defineProperty(exports, "ImageConfigContext", {
-        enumerable: true,
-        get: function () {
-          return ImageConfigContext;
-        },
-      });
-      const _interop_require_default = __webpack_require__(8182);
-      const _react = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(2786)
-      );
-      const _imageconfig = __webpack_require__(936);
-      const ImageConfigContext = _react.default.createContext(
-        _imageconfig.imageConfigDefault
-      );
-      if (false) {
-      } //# sourceMappingURL=image-config-context.shared-runtime.js.map
-
-      /***/
-    },
-
-    /***/ 7824: /***/ (__unused_webpack_module, exports) => {
-      Object.defineProperty(exports, "__esModule", {
-        value: true,
-      });
-      Object.defineProperty(exports, "isInAmpMode", {
-        enumerable: true,
-        get: function () {
-          return isInAmpMode;
-        },
-      });
-      function isInAmpMode(param) {
-        let {
-          ampFirst = false,
-          hybrid = false,
-          hasQuery = false,
-        } = param === void 0 ? {} : param;
-        return ampFirst || (hybrid && hasQuery);
-      } //# sourceMappingURL=amp-mode.js.map
-
-      /***/
-    },
-
-    /***/ 8863: /***/ (module, exports, __webpack_require__) => {
+    /***/ 7009: /***/ (module, exports, __webpack_require__) => {
       /* __next_internal_client_entry_do_not_use__  cjs */
       Object.defineProperty(exports, "__esModule", {
         value: true,
@@ -1071,27 +901,27 @@
           return Image;
         },
       });
-      const _interop_require_default = __webpack_require__(8182);
-      const _interop_require_wildcard = __webpack_require__(8319);
-      const _jsxruntime = __webpack_require__(1050);
+      const _interop_require_default = __webpack_require__(2726);
+      const _interop_require_wildcard = __webpack_require__(2527);
+      const _jsxruntime = __webpack_require__(8204);
       const _react = /*#__PURE__*/ _interop_require_wildcard._(
-        __webpack_require__(2786)
+        __webpack_require__(2224)
       );
       const _reactdom = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(1407)
+        __webpack_require__(1345)
       );
       const _head = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(1796)
+        __webpack_require__(5402)
       );
-      const _getimgprops = __webpack_require__(2843);
-      const _imageconfig = __webpack_require__(936);
-      const _imageconfigcontextsharedruntime = __webpack_require__(7480);
-      const _warnonce = __webpack_require__(2854);
-      const _routercontextsharedruntime = __webpack_require__(7053);
+      const _getimgprops = __webpack_require__(6745);
+      const _imageconfig = __webpack_require__(9278);
+      const _imageconfigcontextsharedruntime = __webpack_require__(9690);
+      const _warnonce = __webpack_require__(4504);
+      const _routercontextsharedruntime = __webpack_require__(2843);
       const _imageloader = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(7281)
+        __webpack_require__(3003)
       );
-      const _usemergedref = __webpack_require__(22);
+      const _usemergedref = __webpack_require__(1912);
       // This is replaced by webpack define plugin
       const configEnv = {
         deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
@@ -1416,5 +1246,175 @@
 
       /***/
     },
+
+    /***/ 9278: /***/ (__unused_webpack_module, exports) => {
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
+      });
+      0 && 0;
+      function _export(target, all) {
+        for (var name in all)
+          Object.defineProperty(target, name, {
+            enumerable: true,
+            get: all[name],
+          });
+      }
+      _export(exports, {
+        VALID_LOADERS: function () {
+          return VALID_LOADERS;
+        },
+        imageConfigDefault: function () {
+          return imageConfigDefault;
+        },
+      });
+      const VALID_LOADERS = [
+        "default",
+        "imgix",
+        "cloudinary",
+        "akamai",
+        "custom",
+      ];
+      const imageConfigDefault = {
+        deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
+        imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
+        path: "/_next/image",
+        loader: "default",
+        loaderFile: "",
+        domains: [],
+        disableStaticImages: false,
+        minimumCacheTTL: 60,
+        formats: ["image/webp"],
+        dangerouslyAllowSVG: false,
+        contentSecurityPolicy: "script-src 'none'; frame-src 'none'; sandbox;",
+        contentDispositionType: "attachment",
+        localPatterns: undefined,
+        remotePatterns: [],
+        qualities: undefined,
+        unoptimized: false,
+      }; //# sourceMappingURL=image-config.js.map
+
+      /***/
+    },
+
+    /***/ 9319: /***/ (
+      __unused_webpack_module,
+      exports,
+      __webpack_require__
+    ) => {
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
+      });
+      Object.defineProperty(exports, "default", {
+        enumerable: true,
+        get: function () {
+          return SideEffect;
+        },
+      });
+      const _react = __webpack_require__(2224);
+      const isServer = "object" === "undefined";
+      const useClientOnlyLayoutEffect = isServer
+        ? () => {}
+        : _react.useLayoutEffect;
+      const useClientOnlyEffect = isServer ? () => {} : _react.useEffect;
+      function SideEffect(props) {
+        const { headManager, reduceComponentsToState } = props;
+        function emitChange() {
+          if (headManager && headManager.mountedInstances) {
+            const headElements = _react.Children.toArray(
+              Array.from(headManager.mountedInstances).filter(Boolean)
+            );
+            headManager.updateHead(
+              reduceComponentsToState(headElements, props)
+            );
+          }
+        }
+        if (isServer) {
+          var _headManager_mountedInstances;
+          headManager == null
+            ? void 0
+            : (_headManager_mountedInstances = headManager.mountedInstances) ==
+              null
+            ? void 0
+            : _headManager_mountedInstances.add(props.children);
+          emitChange();
+        }
+        useClientOnlyLayoutEffect(() => {
+          var _headManager_mountedInstances;
+          headManager == null
+            ? void 0
+            : (_headManager_mountedInstances = headManager.mountedInstances) ==
+              null
+            ? void 0
+            : _headManager_mountedInstances.add(props.children);
+          return () => {
+            var _headManager_mountedInstances;
+            headManager == null
+              ? void 0
+              : (_headManager_mountedInstances =
+                  headManager.mountedInstances) == null
+              ? void 0
+              : _headManager_mountedInstances.delete(props.children);
+          };
+        });
+        // We need to call `updateHead` method whenever the `SideEffect` is trigger in all
+        // life-cycles: mount, update, unmount. However, if there are multiple `SideEffect`s
+        // being rendered, we only trigger the method from the last one.
+        // This is ensured by keeping the last unflushed `updateHead` in the `_pendingUpdate`
+        // singleton in the layout effect pass, and actually trigger it in the effect pass.
+        useClientOnlyLayoutEffect(() => {
+          if (headManager) {
+            headManager._pendingUpdate = emitChange;
+          }
+          return () => {
+            if (headManager) {
+              headManager._pendingUpdate = emitChange;
+            }
+          };
+        });
+        useClientOnlyEffect(() => {
+          if (headManager && headManager._pendingUpdate) {
+            headManager._pendingUpdate();
+            headManager._pendingUpdate = null;
+          }
+          return () => {
+            if (headManager && headManager._pendingUpdate) {
+              headManager._pendingUpdate();
+              headManager._pendingUpdate = null;
+            }
+          };
+        });
+        return null;
+      } //# sourceMappingURL=side-effect.js.map
+
+      /***/
+    },
+
+    /***/ 9690: /***/ (
+      __unused_webpack_module,
+      exports,
+      __webpack_require__
+    ) => {
+      Object.defineProperty(exports, "__esModule", {
+        value: true,
+      });
+      Object.defineProperty(exports, "ImageConfigContext", {
+        enumerable: true,
+        get: function () {
+          return ImageConfigContext;
+        },
+      });
+      const _interop_require_default = __webpack_require__(2726);
+      const _react = /*#__PURE__*/ _interop_require_default._(
+        __webpack_require__(2224)
+      );
+      const _imageconfig = __webpack_require__(9278);
+      const ImageConfigContext = _react.default.createContext(
+        _imageconfig.imageConfigDefault
+      );
+      if (false) {
+      } //# sourceMappingURL=image-config-context.shared-runtime.js.map
+
+      /***/
+    },
   },
 ]);
Diff for 9304-HASH.js

Diff too large to display

Diff for main-HASH.js

Diff too large to display

Commit: 9c35b1d

@bgub bgub force-pushed the feat/type-validation-new branch from 2528e5b to afe53e9 Compare August 2, 2025 01:03
@bgub bgub force-pushed the advanced-typegen branch from 13fc670 to 4fa5201 Compare August 2, 2025 01:03
@bgub bgub force-pushed the feat/type-validation-new branch from afe53e9 to 851e0c4 Compare August 2, 2025 07:12
@bgub bgub force-pushed the advanced-typegen branch from 4fa5201 to 488812b Compare August 2, 2025 07:12
@bgub bgub force-pushed the feat/type-validation-new branch from 851e0c4 to 5155e1a Compare August 2, 2025 07:35
@bgub bgub force-pushed the advanced-typegen branch from 488812b to 347fa9e Compare August 2, 2025 07:35
@bgub bgub force-pushed the advanced-typegen branch from 347fa9e to 65d2a75 Compare August 2, 2025 08:04
@bgub bgub force-pushed the feat/type-validation-new branch from 5155e1a to f816f27 Compare August 2, 2025 08:04
@bgub bgub force-pushed the feat/type-validation-new branch from 62c329e to 1c18354 Compare August 12, 2025 17:46
@bgub bgub force-pushed the advanced-typegen branch from 105a651 to 38a5d12 Compare August 12, 2025 17:46
Comment on lines +1323 to +1358
const foundParams = Array.from(
route.matchAll(/\[(.*?)\]/g),
(match) => match[1]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern used to extract dynamic route parameters incorrectly handles catch-all routes, extracting ...slug instead of slug from [...slug] routes and [...slug from [[...slug]] routes.

View Details
📝 Patch Details
diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts
index 77655ded60..59e3fff385 100644
--- a/packages/next/src/build/index.ts
+++ b/packages/next/src/build/index.ts
@@ -1320,10 +1320,34 @@ export default async function build(
           // Find layouts that could be root layouts (have dynamic segments)
           const layoutsWithParams = layoutRoutes
             .map(({ route }) => {
-              const foundParams = Array.from(
-                route.matchAll(/\[(.*?)\]/g),
-                (match) => match[1]
+              const foundParams = []
+              
+              // First, handle optional catch-all routes: [[...slug]] -> slug
+              const optionalCatchAllMatches = route.match(/\[\[\.\.\.([^\]]+)\]\]/g)
+              if (optionalCatchAllMatches) {
+                optionalCatchAllMatches.forEach(match => {
+                  // Extract the parameter name from [[...paramName]]
+                  const param = match.slice(5, -2) // Remove [[... and ]]
+                  foundParams.push(param)
+                })
+                // Remove these from the route to avoid double processing
+                route = route.replace(/\[\[\.\.\.([^\]]+)\]\]/g, '')
+              }
+              
+              // Then handle regular dynamic segments and catch-all routes
+              const regularMatches = Array.from(
+                route.matchAll(/\[([^\]]+)\]/g),
+                (match) => {
+                  let param = match[1]
+                  // Handle catch-all routes: [...slug] -> slug
+                  if (param.startsWith('...')) {
+                    param = param.slice(3)
+                  }
+                  return param
+                }
               )
+              
+              foundParams.push(...regularMatches)
               return { route, params: foundParams }
             })
             .filter(({ params }) => params.length > 0)

Analysis

The regular expression /\[(.*?)\]/g used to extract parameter names from dynamic route segments doesn't properly handle Next.js catch-all routes. For a route like /[...slug], it extracts ...slug instead of just slug. For optional catch-all routes like /[[...slug]], it extracts [...slug instead of slug. This results in incorrect parameter names being included in the generated server types, which would cause TypeScript compilation errors when developers try to use the unstable_rootParams() function, since the parameter names would contain invalid characters like ... and [.

The extracted parameters are used in the generateServerTypesFile function to create TypeScript interface properties, so incorrect names like ...slug would generate invalid TypeScript syntax like { "...slug": string } instead of the correct { slug: string }.


Recommendation

Replace the parameter extraction logic with proper parsing that handles all Next.js dynamic route types:

const foundParams = Array.from(
  route.matchAll(/\[(.*?)\]/g),
  (match) => {
    let param = match[1]
    // Handle catch-all routes: [...slug] -> slug
    if (param.startsWith('...')) {
      param = param.slice(3)
    }
    // Handle optional catch-all routes: [[...slug]] -> slug  
    if (param.startsWith('[...') && param.endsWith(']')) {
      param = param.slice(4, -1)
    }
    return param
  }
)

Alternatively, use separate regex patterns for each route type to be more explicit about the parsing logic.

@bgub bgub force-pushed the feat/type-validation-new branch from 1c18354 to f9d128a Compare August 12, 2025 20:43
@bgub bgub force-pushed the advanced-typegen branch 2 times, most recently from d982d85 to 81b4e3b Compare August 12, 2025 20:51
@bgub bgub force-pushed the feat/type-validation-new branch from f9d128a to 6c0cbd9 Compare August 12, 2025 20:51
@bgub bgub force-pushed the advanced-typegen branch from 81b4e3b to 7c6c001 Compare August 12, 2025 22:08
@bgub bgub force-pushed the feat/type-validation-new branch 2 times, most recently from 89529bb to 581a268 Compare August 12, 2025 22:54
@bgub bgub force-pushed the advanced-typegen branch from 7c6c001 to 007f9d0 Compare August 12, 2025 22:54
@bgub bgub changed the base branch from feat/type-validation-new to graphite-base/82259 August 12, 2025 23:55
@bgub bgub force-pushed the advanced-typegen branch from 007f9d0 to 1f85ef5 Compare August 13, 2025 05:16
@bgub bgub force-pushed the graphite-base/82259 branch from 581a268 to b2f876c Compare August 13, 2025 05:16
@bgub bgub changed the base branch from graphite-base/82259 to feat/type-validation-new August 13, 2025 05:16
if (seconds === undefined) {
return 'default'
}
if (seconds >= 0xfffffffe) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The formatTimespanWithSeconds function incorrectly checks for 0xfffffffe (4294967294) when it should check for the INFINITE_CACHE constant, and the condition uses >= when it should be === for exact comparison.

View Details
📝 Patch Details
diff --git a/packages/next/src/server/lib/router-utils/typegen.ts b/packages/next/src/server/lib/router-utils/typegen.ts
index ac99725e6b..a37530f13e 100644
--- a/packages/next/src/server/lib/router-utils/typegen.ts
+++ b/packages/next/src/server/lib/router-utils/typegen.ts
@@ -1,6 +1,7 @@
 import type { RouteTypesManifest } from './route-types-utils'
 import { isDynamicRoute } from '../../../shared/lib/router/utils/is-dynamic'
 import type { CacheLife } from '../../../server/use-cache/cache-life'
+import { INFINITE_CACHE } from '../../../lib/constants'
 
 function generateRouteTypes(routesManifest: RouteTypesManifest): string {
   const appRoutes = Object.keys(routesManifest.appRoutes).sort()
@@ -398,7 +399,7 @@ function formatTimespanWithSeconds(seconds: undefined | number): string {
   if (seconds === undefined) {
     return 'default'
   }
-  if (seconds >= 0xfffffffe) {
+  if (seconds === INFINITE_CACHE) {
     return 'never'
   }
   const text = seconds + ' seconds'

Analysis

In the formatTimespanWithSeconds function, the condition if (seconds >= 0xfffffffe) is problematic for several reasons:

  1. Magic number usage: The code uses the literal 0xfffffffe instead of the defined INFINITE_CACHE constant (which has the same value), making the code less maintainable and potentially inconsistent if the constant value changes.

  2. Incorrect comparison operator: Using >= instead of === means that any value greater than 0xfffffffe (4294967294) would be treated as "never", which could lead to unexpected behavior if larger values are passed. Given that this represents a cache duration in seconds, values larger than the intended "infinite" marker should not be treated the same way.

  3. Type safety concern: The function accepts undefined | number, but the comparison assumes a number. While this works in JavaScript due to type coercion, it's better to be explicit.

The same pattern appears in multiple places in the file (lines 465, 483, 492) in the generateCacheLifeTypesFile function, but those instances are consistent with the original NextTypesPlugin implementation and use the correct >= operator for their context (they represent thresholds, not exact matches).


Recommendation

Import the INFINITE_CACHE constant from '../../../lib/constants' and change the condition to if (seconds === INFINITE_CACHE) for an exact match, or if the behavior should include values greater than the infinite cache marker, document why >= is intentional and consider using the constant instead of the magic number.

@bgub bgub force-pushed the advanced-typegen branch from 1f85ef5 to 4d1bf61 Compare August 13, 2025 06:16
@bgub bgub force-pushed the feat/type-validation-new branch 2 times, most recently from a6dead1 to 6e1f178 Compare August 13, 2025 07:53
@bgub bgub force-pushed the advanced-typegen branch 2 times, most recently from dd5202b to 638238d Compare August 13, 2025 08:20
@bgub bgub force-pushed the feat/type-validation-new branch from 6e1f178 to 00d681a Compare August 13, 2025 08:20
@bgub bgub force-pushed the advanced-typegen branch from 638238d to 567af54 Compare August 13, 2025 09:01
@bgub bgub force-pushed the feat/type-validation-new branch from 00d681a to 1b65563 Compare August 13, 2025 09:01
Comment on lines +1356 to +1358
const foundParams = Array.from(
route.matchAll(/\[(.*?)\]/g),
(match) => match[1]
Copy link
Contributor

@vercel vercel bot Aug 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The route parameter extraction regex doesn't distinguish between optional ([[...param]]) and required ([param]) dynamic segments, but all extracted parameters are marked as non-optional in the server types.

View Details
📝 Patch Details
diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts
index 32cb4fffff..59e41cbeb6 100644
--- a/packages/next/src/build/index.ts
+++ b/packages/next/src/build/index.ts
@@ -1354,8 +1354,18 @@ export default async function build(
           const layoutsWithParams = layoutRoutes
             .map(({ route }) => {
               const foundParams = Array.from(
-                route.matchAll(/\[(.*?)\]/g),
-                (match) => match[1]
+                route.matchAll(/(\[\[.*?\]\]|\[.*?\])/g),
+                (match) => {
+                  const fullMatch = match[1]
+                  const isOptional = fullMatch.startsWith("[[") && fullMatch.endsWith("]]")
+                  const paramContent = isOptional 
+                    ? fullMatch.slice(2, -2)  // Remove [[ ]]
+                    : fullMatch.slice(1, -1)  // Remove [ ]
+                  const paramName = paramContent.startsWith("...") 
+                    ? paramContent.slice(3)   // Remove ...
+                    : paramContent
+                  return { param: paramName, optional: isOptional }
+                }
               )
               return { route, params: foundParams }
             })
diff --git a/packages/next/src/server/lib/router-utils/route-types-utils.ts b/packages/next/src/server/lib/router-utils/route-types-utils.ts
index 3fae5a4199..f253f2f5d0 100644
--- a/packages/next/src/server/lib/router-utils/route-types-utils.ts
+++ b/packages/next/src/server/lib/router-utils/route-types-utils.ts
@@ -369,10 +369,15 @@ export async function writeServerTypesFile(
     const allRootParams: { param: string; optional: boolean }[] = []
 
     for (const [, params] of Object.entries(manifest.collectedRootParams)) {
-      for (const param of params) {
-        // All root layout params are required (not optional)
-        // since they define the top-level structure of the app
-        allRootParams.push({ param, optional: false })
+      for (const paramObj of params) {
+        // Handle both old string format and new object format for backward compatibility
+        if (typeof paramObj === 'string') {
+          // Old format: all params were treated as required
+          allRootParams.push({ param: paramObj, optional: false })
+        } else {
+          // New format: use the optional flag from the parameter extraction
+          allRootParams.push({ param: paramObj.param, optional: paramObj.optional })
+        }
       }
     }
 

Analysis

The parameter extraction logic uses a simple regex /\\[(.*?)\\]/g to extract parameter names from layout routes, which captures the content inside any brackets but doesn't differentiate between optional catch-all routes ([[...param]]), required catch-all routes ([...param]), and regular dynamic routes ([param]). However, in the writeServerTypesFile function at line 375 in route-types-utils.ts, all extracted parameters are hardcoded as optional: false.

This means that optional catch-all parameters (which should be optional in the TypeScript interface) will be incorrectly typed as required parameters in the generated unstable_rootParams() function signature. This could lead to TypeScript compilation errors when developers try to use optional catch-all routes at the root level, as the type system will expect required parameters that may not exist.

The issue is that the regex extracts "...param" from both [...param] and [[...param]], but the code doesn't inspect the original route string to determine if the parameter was wrapped in double brackets (indicating it's optional).


Recommendation

Modify the parameter extraction logic to properly detect optional parameters by checking for double brackets. Replace the current extraction logic with code that:

  1. Use a regex that captures the full bracket structure: /(\[\[.*?\]\]|\[.*?\])/g
  2. For each match, determine if it's optional (double brackets) or required (single brackets)
  3. Parse the parameter name correctly, handling catch-all syntax (...param)
  4. Pass the correct optional flag to the server types generation

Example fix:

const foundParams = Array.from(
  route.matchAll(/(\[\[.*?\]\]|\[.*?\])/g),
  (match) => {
    const fullMatch = match[1]
    const isOptional = fullMatch.startsWith('[[') && fullMatch.endsWith(']]')
    const paramContent = isOptional 
      ? fullMatch.slice(2, -2)  // Remove [[ ]]
      : fullMatch.slice(1, -1)  // Remove [ ]
    const paramName = paramContent.startsWith('...') 
      ? paramContent.slice(3)   // Remove ...
      : paramContent
    return { param: paramName, optional: isOptional }
  }
)

@bgub bgub force-pushed the advanced-typegen branch from 567af54 to 9c35b1d Compare August 13, 2025 09:41
@bgub bgub force-pushed the feat/type-validation-new branch from 1b65563 to 884276a Compare August 13, 2025 09:41
Base automatically changed from feat/type-validation-new to canary August 13, 2025 20:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants