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
5 changes: 4 additions & 1 deletion packages/next/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -927,5 +927,8 @@
"926": "Optional route parameters are not yet supported (\"[%s]\") in route \"%s\".",
"927": "No debug targets found",
"928": "Unable to get server address",
"929": "No pages or app directory found."
"929": "No pages or app directory found.",
"930": "Expected a dynamic route, but got a static route: %s",
"931": "Unexpected empty path segments match for a route \"%s\" with param \"%s\" of type \"%s\"",
"932": "Could not resolve param value for segment: %s"
}
138 changes: 26 additions & 112 deletions packages/next/src/build/segment-config/app/app-segments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ import {
getLayoutOrPageModule,
type LoaderTree,
} from '../../../server/lib/app-dir-module'
import { PAGE_SEGMENT_KEY } from '../../../shared/lib/segment'
import type { FallbackRouteParam } from '../../static-paths/types'
import { createFallbackRouteParam } from '../../static-paths/utils'
import type { DynamicParamTypes } from '../../../shared/lib/app-router-types'

type GenerateStaticParams = (options: { params?: Params }) => Promise<Params[]>
Expand Down Expand Up @@ -63,14 +60,7 @@ export type AppSegment = {
paramType: DynamicParamTypes | undefined
filePath: string | undefined
config: AppSegmentConfig | undefined
isDynamicSegment: boolean
generateStaticParams: GenerateStaticParams | undefined

/**
* Whether this segment is a parallel route segment or descends from a
* parallel route segment.
*/
isParallelRouteSegment: boolean | undefined
}

/**
Expand All @@ -82,79 +72,57 @@ export type AppSegment = {
async function collectAppPageSegments(routeModule: AppPageRouteModule) {
// We keep track of unique segments, since with parallel routes, it's possible
// to see the same segment multiple times.
const uniqueSegments = new Map<string, AppSegment>()
const segments: AppSegment[] = []

// Queue will store tuples of [loaderTree, currentSegments, isParallelRouteSegment]
type QueueItem = [
loaderTree: LoaderTree,
currentSegments: AppSegment[],
isParallelRouteSegment: boolean,
]
const queue: QueueItem[] = [[routeModule.userland.loaderTree, [], false]]
// Queue will store loader trees.
const queue: LoaderTree[] = [routeModule.userland.loaderTree]

while (queue.length > 0) {
const [loaderTree, currentSegments, isParallelRouteSegment] = queue.shift()!
const loaderTree = queue.shift()!
const [name, parallelRoutes] = loaderTree

// Process current node
const { mod: userland, filePath } = await getLayoutOrPageModule(loaderTree)
const isClientComponent = userland && isClientReference(userland)

const { param: paramName, type: paramType } = getSegmentParam(name) ?? {}
const param = getSegmentParam(name)

const segment: AppSegment = {
name,
paramName,
paramType,
paramName: param?.paramName,
paramType: param?.paramType,
filePath,
config: undefined,
isDynamicSegment: !!paramName,
generateStaticParams: undefined,
isParallelRouteSegment,
}

// Only server components can have app segment configurations
if (!isClientComponent) {
attach(segment, userland, routeModule.definition.pathname)
}

// Create a unique key for the segment
const segmentKey = getSegmentKey(segment)
if (!uniqueSegments.has(segmentKey)) {
uniqueSegments.set(segmentKey, segment)
}

const updatedSegments = [...currentSegments, segment]

// If this is a page segment, we've reached a leaf node
if (name === PAGE_SEGMENT_KEY) {
// Add all segments in the current path, preferring non-parallel segments
updatedSegments.forEach((seg) => {
const key = getSegmentKey(seg)
if (!uniqueSegments.has(key)) {
uniqueSegments.set(key, seg)
}
})
// If this segment doesn't already exist, then add it to the segments array.
// The list of segments is short so we just use a list traversal to check
// for duplicates and spare us needing to maintain the string key.
if (
segments.every(
(s) =>
s.name !== segment.name ||
s.paramName !== segment.paramName ||
s.paramType !== segment.paramType ||
s.filePath !== segment.filePath
)
) {
segments.push(segment)
}

// Add all parallel routes to the queue
for (const parallelRouteKey in parallelRoutes) {
const parallelRoute = parallelRoutes[parallelRouteKey]
queue.push([
parallelRoute,
updatedSegments,
// A parallel route segment is one that descends from a segment that is
// not children or descends from a parallel route segment.
isParallelRouteSegment || parallelRouteKey !== 'children',
])
for (const parallelRoute of Object.values(parallelRoutes)) {
queue.push(parallelRoute)
}
}

return Array.from(uniqueSegments.values())
}

function getSegmentKey(segment: AppSegment) {
return `${segment.name}-${segment.filePath ?? ''}-${segment.paramName ?? ''}-${segment.isParallelRouteSegment ? 'pr' : 'np'}`
return segments
}

/**
Expand All @@ -174,17 +142,15 @@ function collectAppRouteSegments(

// Generate all the segments.
const segments: AppSegment[] = parts.map((name) => {
const { param: paramName, type: paramType } = getSegmentParam(name) ?? {}
const param = getSegmentParam(name)

return {
name,
paramName,
paramType,
paramName: param?.paramName,
paramType: param?.paramType,
filePath: undefined,
isDynamicSegment: !!paramName,
config: undefined,
generateStaticParams: undefined,
isParallelRouteSegment: undefined,
} satisfies AppSegment
})

Expand Down Expand Up @@ -221,55 +187,3 @@ export function collectSegments(
'Expected a route module to be one of app route or page'
)
}

/**
* Collects the fallback route params for a given app page route module. This is
* a variant of the `collectSegments` function that only collects the fallback
* route params without importing anything.
*
* @param routeModule the app page route module
* @returns the fallback route params for the app page route module
*/
export function collectFallbackRouteParams(
routeModule: AppPageRouteModule
): readonly FallbackRouteParam[] {
const uniqueSegments = new Map<string, FallbackRouteParam>()

// Queue will store tuples of [loaderTree, isParallelRouteSegment]
type QueueItem = [loaderTree: LoaderTree, isParallelRouteSegment: boolean]
const queue: QueueItem[] = [[routeModule.userland.loaderTree, false]]

while (queue.length > 0) {
const [loaderTree, isParallelRouteSegment] = queue.shift()!
const [name, parallelRoutes] = loaderTree

// Handle this segment (if it's a dynamic segment param).
const segmentParam = getSegmentParam(name)
if (segmentParam) {
const key = `${name}-${segmentParam.param}-${isParallelRouteSegment ? 'pr' : 'np'}`
if (!uniqueSegments.has(key)) {
uniqueSegments.set(
key,
createFallbackRouteParam(
segmentParam.param,
segmentParam.type,
isParallelRouteSegment
)
)
}
}

// Add all of this segment's parallel routes to the queue.
for (const parallelRouteKey in parallelRoutes) {
const parallelRoute = parallelRoutes[parallelRouteKey]
queue.push([
parallelRoute,
// A parallel route segment is one that descends from a segment that is
// not children or descends from a parallel route segment.
isParallelRouteSegment || parallelRouteKey !== 'children',
])
}
}

return Array.from(uniqueSegments.values())
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ function collectAppPageRootParamKeys(
const [name, parallelRoutes, modules] = current

// If this is a dynamic segment, then we collect the param.
const param = getSegmentParam(name)?.param
if (param) {
rootParams.push(param)
const paramName = getSegmentParam(name)?.paramName
if (paramName) {
rootParams.push(paramName)
}

// If this has a layout module, then we've found the root layout because
Expand Down
Loading
Loading