Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions packages/next/src/build/static-paths/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,10 @@ export function resolveParallelRouteParams(
switch (paramType) {
case 'catchall':
case 'optional-catchall':
case 'catchall-intercepted':
case 'catchall-intercepted-(..)(..)':
case 'catchall-intercepted-(.)':
case 'catchall-intercepted-(..)':
case 'catchall-intercepted-(...)':
// If there are any non-parallel fallback route segments, we can't use the
// pathname to derive the value because it's not complete. We can make
// this assumption because routes are resolved left to right.
Expand Down Expand Up @@ -658,7 +661,10 @@ export function resolveParallelRouteParams(
break

case 'dynamic':
case 'dynamic-intercepted':
case 'dynamic-intercepted-(..)(..)':
case 'dynamic-intercepted-(.)':
case 'dynamic-intercepted-(..)':
case 'dynamic-intercepted-(...)':
// For regular dynamic parameters, take the segment at this depth
if (depth < pathSegments.length) {
const pathSegment = pathSegments[depth]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,17 @@ export function hasInterceptionRouteInCurrentTree([
parallelRoutes,
]: FlightRouterState): boolean {
// If we have a dynamic segment, it's marked as an interception route by the presence of the `i` suffix.
if (Array.isArray(segment) && (segment[2] === 'di' || segment[2] === 'ci')) {
if (
Array.isArray(segment) &&
(segment[2] === 'di(..)(..)' ||
segment[2] === 'ci(..)(..)' ||
segment[2] === 'di(.)' ||
segment[2] === 'ci(.)' ||
segment[2] === 'di(..)' ||
segment[2] === 'ci(..)' ||
segment[2] === 'di(...)' ||
segment[2] === 'ci(...)')
) {
return true
}

Expand Down
41 changes: 37 additions & 4 deletions packages/next/src/client/route-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,29 @@ export function parseDynamicParamFromURLPart(
// This needs to match the behavior in get-dynamic-param.ts.
switch (paramType) {
// Catchalls
case 'c':
case 'ci': {
case 'c': {
// Catchalls receive all the remaining URL parts. If there are no
// remaining pathname parts, return an empty array.
return partIndex < pathnameParts.length
? pathnameParts.slice(partIndex).map((s) => encodeURIComponent(s))
: []
}
// Catchall intercepted
case 'ci(..)(..)':
case 'ci(.)':
case 'ci(..)':
case 'ci(...)': {
const prefix = paramType.length - 2
return partIndex < pathnameParts.length
? pathnameParts.slice(partIndex).map((s, i) => {
if (i === 0) {
return encodeURIComponent(s.slice(prefix))
}

return encodeURIComponent(s)
})
: []
}
// Optional catchalls
case 'oc': {
// Optional catchalls receive all the remaining URL parts, unless this is
Expand All @@ -77,8 +92,7 @@ export function parseDynamicParamFromURLPart(
: null
}
// Dynamic
case 'd':
case 'di': {
case 'd': {
if (partIndex >= pathnameParts.length) {
// The route tree expected there to be more parts in the URL than there
// actually are. This could happen if the x-nextjs-rewritten-path header
Expand All @@ -91,6 +105,25 @@ export function parseDynamicParamFromURLPart(
}
return encodeURIComponent(pathnameParts[partIndex])
}
// Dynamic intercepted
case 'di(..)(..)':
case 'di(.)':
case 'di(..)':
case 'di(...)': {
const prefix = paramType.length - 2
if (partIndex >= pathnameParts.length) {
// The route tree expected there to be more parts in the URL than there
// actually are. This could happen if the x-nextjs-rewritten-path header
// is incorrectly set, or potentially due to bug in Next.js. TODO:
// Should this be a hard error? During a prefetch, we can just abort.
// During a client navigation, we could trigger a hard refresh. But if
// it happens during initial render, we don't really have any
// recovery options.
return ''
}

return encodeURIComponent(pathnameParts[partIndex].slice(prefix))
}
default:
paramType satisfies never
return ''
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ export const dynamicParamTypes: Record<
DynamicParamTypesShort
> = {
catchall: 'c',
'catchall-intercepted': 'ci',
'catchall-intercepted-(..)(..)': 'ci(..)(..)',
'catchall-intercepted-(.)': 'ci(.)',
'catchall-intercepted-(..)': 'ci(..)',
'catchall-intercepted-(...)': 'ci(...)',
'optional-catchall': 'oc',
dynamic: 'd',
'dynamic-intercepted': 'di',
'dynamic-intercepted-(..)(..)': 'di(..)(..)',
'dynamic-intercepted-(.)': 'di(.)',
'dynamic-intercepted-(..)': 'di(..)',
'dynamic-intercepted-(...)': 'di(...)',
}
14 changes: 13 additions & 1 deletion packages/next/src/server/app-render/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,19 @@ import type { IncomingMessage } from 'http'
import type { RenderResumeDataCache } from '../resume-data-cache/resume-data-cache'
import type { ServerCacheStatus } from '../../next-devtools/dev-overlay/cache-indicator'

const dynamicParamTypesSchema = s.enums(['c', 'ci', 'oc', 'd', 'di'])
const dynamicParamTypesSchema = s.enums([
'c',
'ci(..)(..)',
'ci(.)',
'ci(..)',
'ci(...)',
'oc',
'd',
'di(..)(..)',
'di(.)',
'di(..)',
'di(...)',
])

const segmentSchema = s.union([
s.string(),
Expand Down
10 changes: 8 additions & 2 deletions packages/next/src/server/dev/on-demand-entry-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,18 @@ function convertDynamicParamTypeToSyntax(
) {
switch (dynamicParamTypeShort) {
case 'c':
case 'ci':
case 'ci(..)(..)':
case 'ci(.)':
case 'ci(..)':
case 'ci(...)':
return `[...${param}]`
case 'oc':
return `[[...${param}]]`
case 'd':
case 'di':
case 'di(..)(..)':
case 'di(.)':
case 'di(..)':
case 'di(...)':
return `[${param}]`
default:
throw new Error('Unknown dynamic param type')
Expand Down
25 changes: 21 additions & 4 deletions packages/next/src/shared/lib/app-router-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,29 @@ export type ReadyCacheNode = {

export type DynamicParamTypes =
| 'catchall'
| 'catchall-intercepted'
| 'catchall-intercepted-(..)(..)'
| 'catchall-intercepted-(.)'
| 'catchall-intercepted-(..)'
| 'catchall-intercepted-(...)'
| 'optional-catchall'
| 'dynamic'
| 'dynamic-intercepted'

export type DynamicParamTypesShort = 'c' | 'ci' | 'oc' | 'd' | 'di'
| 'dynamic-intercepted-(..)(..)'
| 'dynamic-intercepted-(.)'
| 'dynamic-intercepted-(..)'
| 'dynamic-intercepted-(...)'

export type DynamicParamTypesShort =
| 'c'
| 'ci(..)(..)'
| 'ci(.)'
| 'ci(..)'
| 'ci(...)'
| 'oc'
| 'd'
| 'di(..)(..)'
| 'di(.)'
| 'di(..)'
| 'di(...)'

export type Segment =
| string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ describe('getDynamicParam', () => {
const params: Params = {}

expect(() => {
getDynamicParam(params, 'slug', 'di', null)
getDynamicParam(params, 'slug', 'di(..)(..)', null)
}).toThrow(InvariantError)
expect(() => {
getDynamicParam(params, 'slug', 'di', null)
getDynamicParam(params, 'slug', 'di(..)(..)', null)
}).toThrow(
'Invariant: Missing value for segment key: "slug" with dynamic param type: di. This is a bug in Next.js.'
'Invariant: Missing value for segment key: "slug" with dynamic param type: di(..)(..). This is a bug in Next.js.'
)
})
})
Expand Down Expand Up @@ -113,13 +113,13 @@ describe('getDynamicParam', () => {

it('should handle catchall intercepted (ci) with array values', () => {
const params: Params = { path: ['photo', '123'] }
const result = getDynamicParam(params, 'path', 'ci', null)
const result = getDynamicParam(params, 'path', 'ci(..)(..)', null)

expect(result).toEqual({
param: 'path',
value: ['photo', '123'],
type: 'ci',
treeSegment: ['path', 'photo/123', 'ci'],
type: 'ci(..)(..)',
treeSegment: ['path', 'photo/123', 'ci(..)(..)'],
})
})

Expand Down
10 changes: 8 additions & 2 deletions packages/next/src/shared/lib/router/utils/get-dynamic-param.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ export function interpolateParallelRouteParams(
switch (segmentParam.type) {
case 'catchall':
case 'optional-catchall':
case 'catchall-intercepted':
case 'catchall-intercepted-(..)(..)':
case 'catchall-intercepted-(.)':
case 'catchall-intercepted-(..)':
case 'catchall-intercepted-(...)':
// For catchall parameters, take all remaining segments from this depth
const remainingSegments = pathSegments.slice(depth)

Expand All @@ -92,7 +95,10 @@ export function interpolateParallelRouteParams(
}
break
case 'dynamic':
case 'dynamic-intercepted':
case 'dynamic-intercepted-(..)(..)':
case 'dynamic-intercepted-(.)':
case 'dynamic-intercepted-(..)':
case 'dynamic-intercepted-(...)':
// For regular dynamic parameters, take the segment at this depth
if (depth < pathSegments.length) {
const pathSegment = pathSegments[depth]
Expand Down
31 changes: 25 additions & 6 deletions packages/next/src/shared/lib/router/utils/get-segment-param.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,18 @@ export function getSegmentParam(segment: string): {

if (segment.startsWith('[...') && segment.endsWith(']')) {
return {
type: interceptionMarker ? 'catchall-intercepted' : 'catchall',
type: interceptionMarker
? `catchall-intercepted-${interceptionMarker}`
: 'catchall',
param: segment.slice(4, -1),
}
}

if (segment.startsWith('[') && segment.endsWith(']')) {
return {
type: interceptionMarker ? 'dynamic-intercepted' : 'dynamic',
type: interceptionMarker
? `dynamic-intercepted-${interceptionMarker}`
: 'dynamic',
param: segment.slice(1, -1),
}
}
Expand All @@ -46,10 +50,19 @@ export function getSegmentParam(segment: string): {

export function isCatchAll(
type: DynamicParamTypes
): type is 'catchall' | 'catchall-intercepted' | 'optional-catchall' {
): type is
| 'catchall'
| 'catchall-intercepted-(..)(..)'
| 'catchall-intercepted-(.)'
| 'catchall-intercepted-(..)'
| 'catchall-intercepted-(...)'
| 'optional-catchall' {
return (
type === 'catchall' ||
type === 'catchall-intercepted' ||
type === 'catchall-intercepted-(..)(..)' ||
type === 'catchall-intercepted-(.)' ||
type === 'catchall-intercepted-(..)' ||
type === 'catchall-intercepted-(...)' ||
type === 'optional-catchall'
)
}
Expand All @@ -63,15 +76,21 @@ export function getParamProperties(paramType: DynamicParamTypes): {

switch (paramType) {
case 'catchall':
case 'catchall-intercepted':
case 'catchall-intercepted-(..)(..)':
case 'catchall-intercepted-(.)':
case 'catchall-intercepted-(..)':
case 'catchall-intercepted-(...)':
repeat = true
break
case 'optional-catchall':
repeat = true
optional = true
break
case 'dynamic':
case 'dynamic-intercepted':
case 'dynamic-intercepted-(..)(..)':
case 'dynamic-intercepted-(.)':
case 'dynamic-intercepted-(..)':
case 'dynamic-intercepted-(...)':
break
default:
paramType satisfies never
Expand Down
Loading