This commit is contained in:
Kismet Hasanaj
2026-05-02 20:07:02 +02:00
parent ce8672e283
commit 34dc9aec52
9428 changed files with 1733330 additions and 0 deletions
+783
View File
@@ -0,0 +1,783 @@
import path from 'node:path';
import { AfterRunner } from '../../server/after/run-with-after';
import { createWorkStore } from '../../server/async-storage/work-store';
import { FallbackMode } from '../../lib/fallback';
import { normalizePathname, encodeParam, extractPathnameRouteParamSegments, resolveRouteParamsFromTree } from './utils';
import escapePathDelimiters from '../../shared/lib/router/utils/escape-path-delimiters';
import { createIncrementalCache } from '../../export/helpers/create-incremental-cache';
import { getParamProperties } from '../../shared/lib/router/utils/get-segment-param';
import { throwEmptyGenerateStaticParamsError } from '../../shared/lib/errors/empty-generate-static-params-error';
import { interceptionPrefixFromParamType } from '../../shared/lib/router/utils/interception-prefix-from-param-type';
import { getImplicitTags } from '../../server/lib/implicit-tags';
/**
* Filters out duplicate parameters from a list of parameters.
* This function uses a Map to efficiently store and retrieve unique parameter combinations.
*
* @param childrenRouteParams - The keys of the parameters. These should be sorted to ensure consistent key generation.
* @param routeParams - The list of parameter objects to filter.
* @returns A new array containing only the unique parameter combinations.
*/ export function filterUniqueParams(childrenRouteParams, routeParams) {
// A Map is used to store unique parameter combinations. The key of the Map
// is a string representation of the parameter combination, and the value
// is the actual `Params` object.
const unique = new Map();
// Iterate over each parameter object in the input array.
for (const params of routeParams){
let key = '' // Initialize an empty string to build the unique key for the current `params` object.
;
// Iterate through the `routeParamKeys` (which are assumed to be sorted).
// This consistent order is crucial for generating a stable and unique key
// for each parameter combination.
for (const { paramName: paramKey } of childrenRouteParams){
const value = params[paramKey];
// Construct a part of the key using the parameter key and its value.
// A type prefix (`A:` for Array, `S:` for String, `U:` for undefined) is added to the value
// to prevent collisions. For example, `['a', 'b']` and `'a,b'` would
// otherwise generate the same string representation, leading to incorrect
// deduplication. This ensures that different types with the same string
// representation are treated as distinct.
let valuePart;
if (Array.isArray(value)) {
valuePart = `A:${value.join(',')}`;
} else if (value === undefined) {
valuePart = `U:undefined`;
} else {
valuePart = `S:${value}`;
}
key += `${paramKey}:${valuePart}|`;
}
// If the generated key is not already in the `unique` Map, it means this
// parameter combination is unique so far. Add it to the Map.
if (!unique.has(key)) {
unique.set(key, params);
}
}
// Convert the Map's values (the unique `Params` objects) back into an array
// and return it.
return Array.from(unique.values());
}
/**
* Generates all unique sub-combinations of Route Parameters from a list of Static Parameters.
* This function creates all possible prefixes of the Route Parameters, which is
* useful for generating Static Shells that can serve as Fallback Shells for more specific Route Shells.
*
* When Root Parameters are provided, the function ensures that Static Shells only
* include complete sets of Root Parameters. This prevents generating invalid Static Shells
* that are missing required Root Parameters.
*
* Example with Root Parameters ('lang', 'region') and Route Parameters ('lang', 'region', 'slug'):
*
* Given the following Static Parameters:
* ```
* [
* { lang: 'en', region: 'US', slug: ['home'] },
* { lang: 'en', region: 'US', slug: ['about'] },
* { lang: 'fr', region: 'CA', slug: ['about'] },
* ]
* ```
*
* The result will be:
* ```
* [
* { lang: 'en', region: 'US' }, // Complete Root Parameters
* { lang: 'en', region: 'US', slug: ['home'] },
* { lang: 'en', region: 'US', slug: ['about'] },
* { lang: 'fr', region: 'CA' }, // Complete Root Parameters
* { lang: 'fr', region: 'CA', slug: ['about'] },
* ]
* ```
*
* Note that partial combinations like `{ lang: 'en' }` are NOT generated because
* they don't include the complete set of Root Parameters.
*
* For routes without Root Parameters (e.g., `/[slug]`), all sub-combinations are generated
* as before.
*
* @param childrenRouteParams - The children route params. These should be sorted
* to ensure consistent key generation for the internal Map.
* @param routeParams - The list of Static Parameters to filter.
* @param rootParamKeys - The keys of the Root Parameters. When provided, ensures Static Shells
* include all Root Parameters.
* @returns A new array containing all unique sub-combinations of Route Parameters.
*/ export function generateAllParamCombinations(childrenRouteParams, routeParams, rootParamKeys) {
// A Map is used to store unique combinations of Route Parameters.
// The key of the Map is a string representation of the Route Parameter
// combination, and the value is the `Params` object containing only
// the Route Parameters.
const combinations = new Map();
// Determine the minimum index where all Root Parameters are included.
// This optimization ensures we only generate combinations that include
// a complete set of Root Parameters, preventing invalid Static Shells.
//
// For example, if rootParamKeys = ['lang', 'region'] and routeParamKeys = ['lang', 'region', 'slug']:
// - 'lang' is at index 0, 'region' is at index 1
// - minIndexForCompleteRootParams = max(0, 1) = 1
// - We'll only generate combinations starting from index 1 (which includes both lang and region)
let minIndexForCompleteRootParams = -1;
if (rootParamKeys.length > 0) {
// Find the index of the last Root Parameter in routeParamKeys.
// This tells us the minimum combination length needed to include all Root Parameters.
for (const rootParamKey of rootParamKeys){
const index = childrenRouteParams.findIndex((param)=>param.paramName === rootParamKey);
if (index === -1) {
// Root Parameter not found in Route Parameters - this shouldn't happen in normal cases
// but we handle it gracefully by treating it as if there are no Root Parameters.
// This allows the function to fall back to generating all sub-combinations.
minIndexForCompleteRootParams = -1;
break;
}
// Track the highest index among all Root Parameters.
// This ensures all Root Parameters are included in any generated combination.
minIndexForCompleteRootParams = Math.max(minIndexForCompleteRootParams, index);
}
}
// Iterate over each Static Parameter object in the input array.
// Each params object represents one potential route combination (e.g., { lang: 'en', region: 'US', slug: 'home' })
for (const params of routeParams){
// Generate all possible prefix combinations for this Static Parameter set.
// For routeParamKeys = ['lang', 'region', 'slug'], we'll generate combinations at:
// - i=0: { lang: 'en' }
// - i=1: { lang: 'en', region: 'US' }
// - i=2: { lang: 'en', region: 'US', slug: 'home' }
//
// The iteration order is crucial for generating stable and unique keys
// for each Route Parameter combination.
for(let i = 0; i < childrenRouteParams.length; i++){
// Skip generating combinations that don't include all Root Parameters.
// This prevents creating invalid Static Shells that are missing required Root Parameters.
//
// For example, if Root Parameters are ['lang', 'region'] and minIndexForCompleteRootParams = 1:
// - Skip i=0 (would only include 'lang', missing 'region')
// - Process i=1 and higher (includes both 'lang' and 'region')
if (minIndexForCompleteRootParams >= 0 && i < minIndexForCompleteRootParams) {
continue;
}
// Initialize data structures for building this specific combination
const combination = {};
const keyParts = [];
let hasAllRootParams = true;
// Build the sub-combination with parameters from index 0 to i (inclusive).
// This creates a prefix of the full parameter set, building up combinations incrementally.
//
// For example, if routeParamKeys = ['lang', 'region', 'slug'] and i = 1:
// - j=0: Add 'lang' parameter
// - j=1: Add 'region' parameter
// Result: { lang: 'en', region: 'US' }
for(let j = 0; j <= i; j++){
const { paramName: routeKey } = childrenRouteParams[j];
// Check if the parameter exists in the original params object and has a defined value.
// This handles cases where generateStaticParams doesn't provide all possible parameters,
// or where some parameters are optional/undefined.
if (!params.hasOwnProperty(routeKey) || params[routeKey] === undefined) {
// If this missing parameter is a Root Parameter, mark the combination as invalid.
// Root Parameters are required for Static Shells, so we can't generate partial combinations without them.
if (rootParamKeys.includes(routeKey)) {
hasAllRootParams = false;
}
break;
}
const value = params[routeKey];
combination[routeKey] = value;
// Construct a unique key part for this parameter to enable deduplication.
// We use type prefixes to prevent collisions between different value types
// that might have the same string representation.
//
// Examples:
// - Array ['foo', 'bar'] becomes "A:foo,bar"
// - String "foo,bar" becomes "S:foo,bar"
// - This prevents collisions between ['foo', 'bar'] and "foo,bar"
let valuePart;
if (Array.isArray(value)) {
valuePart = `A:${value.join(',')}`;
} else {
valuePart = `S:${value}`;
}
keyParts.push(`${routeKey}:${valuePart}`);
}
// Build the final unique key by joining all parameter parts.
// This key is used for deduplication in the combinations Map.
// Format: "lang:S:en|region:S:US|slug:A:home,about"
const currentKey = keyParts.join('|');
// Only add the combination if it meets our criteria:
// 1. hasAllRootParams: Contains all required Root Parameters
// 2. !combinations.has(currentKey): Is not a duplicate of an existing combination
//
// This ensures we only generate valid, unique parameter combinations for Static Shells.
if (hasAllRootParams && !combinations.has(currentKey)) {
combinations.set(currentKey, combination);
}
}
}
// Convert the Map's values back into an array and return the final result.
// The Map ensures all combinations are unique, and we return only the
// parameter objects themselves, discarding the internal deduplication keys.
return Array.from(combinations.values());
}
/**
* Calculates the fallback mode based on the given parameters.
*
* @param dynamicParams - Whether dynamic params are enabled.
* @param fallbackRootParams - The root params that are part of the fallback.
* @param baseFallbackMode - The base fallback mode to use.
* @returns The calculated fallback mode.
*/ export function calculateFallbackMode(dynamicParams, fallbackRootParams, baseFallbackMode) {
return dynamicParams ? // perform a blocking static render.
fallbackRootParams.length > 0 ? FallbackMode.BLOCKING_STATIC_RENDER : baseFallbackMode ?? FallbackMode.NOT_FOUND : FallbackMode.NOT_FOUND;
}
/**
* Validates the parameters to ensure they're accessible and have the correct
* types.
*
* @param page - The page to validate.
* @param regex - The route regex.
* @param isRoutePPREnabled - Whether the route has partial prerendering enabled.
* @param pathnameSegments - The keys of the parameters.
* @param rootParamKeys - The keys of the root params.
* @param routeParams - The list of parameters to validate.
* @returns The list of validated parameters.
*/ function validateParams(page, isRoutePPREnabled, pathnameSegments, rootParamKeys, routeParams) {
const valid = [];
// Validate that if there are any root params, that the user has provided at
// least one value for them only if we're using partial prerendering.
if (isRoutePPREnabled && rootParamKeys.length > 0) {
if (routeParams.length === 0 || rootParamKeys.some((key)=>routeParams.some((params)=>!(key in params)))) {
if (rootParamKeys.length === 1) {
throw Object.defineProperty(new Error(`A required root parameter (${rootParamKeys[0]}) was not provided in generateStaticParams for ${page}, please provide at least one value.`), "__NEXT_ERROR_CODE", {
value: "E622",
enumerable: false,
configurable: true
});
}
throw Object.defineProperty(new Error(`Required root params (${rootParamKeys.join(', ')}) were not provided in generateStaticParams for ${page}, please provide at least one value for each.`), "__NEXT_ERROR_CODE", {
value: "E621",
enumerable: false,
configurable: true
});
}
}
for (const params of routeParams){
const item = {};
for (const { paramName: key, paramType } of pathnameSegments){
const { repeat, optional } = getParamProperties(paramType);
let paramValue = params[key];
if (optional && params.hasOwnProperty(key) && (paramValue === null || paramValue === undefined || paramValue === false)) {
paramValue = [];
}
// A parameter is missing, so the rest of the params are not accessible.
// We only support this when the route has partial prerendering enabled.
// This will make it so that the remaining params are marked as missing so
// we can generate a fallback route for them.
if (!paramValue && isRoutePPREnabled) {
break;
}
// Perform validation for the parameter based on whether it's a repeat
// parameter or not.
if (repeat) {
if (!Array.isArray(paramValue)) {
throw Object.defineProperty(new Error(`A required parameter (${key}) was not provided as an array received ${typeof paramValue} in generateStaticParams for ${page}`), "__NEXT_ERROR_CODE", {
value: "E618",
enumerable: false,
configurable: true
});
}
} else {
if (typeof paramValue !== 'string') {
throw Object.defineProperty(new Error(`A required parameter (${key}) was not provided as a string received ${typeof paramValue} in generateStaticParams for ${page}`), "__NEXT_ERROR_CODE", {
value: "E617",
enumerable: false,
configurable: true
});
}
}
item[key] = paramValue;
}
valid.push(item);
}
return valid;
}
/**
* Assigns static shell metadata to each prerendered route.
* This function uses a Trie data structure to efficiently determine whether each route
* should throw an error when its static shell is empty and whether a fallback shell
* can still be completed into a more specific prerendered shell.
*
* A route should not throw on empty static shell if it has child routes in the Trie. For example,
* if we have two routes, `/blog/first-post` and `/blog/[slug]`, the route for
* `/blog/[slug]` should not throw because `/blog/first-post` is a more specific concrete route.
*
* @param prerenderedRoutes - The prerendered routes.
* @param pathnameSegments - The pathname params and whether each one is still
* prerenderable via generateStaticParams.
*/ export function assignStaticShellMetadata(prerenderedRoutes, pathnameSegments, computeRemainingPrerenderableParams) {
// If there are no routes to process, exit early.
if (prerenderedRoutes.length === 0) {
return;
}
// Initialize the root of the Trie. This node represents the starting point
// before any parameters have been considered.
const root = {
children: new Map(),
routes: []
};
// Phase 1: Build the Trie.
// Iterate over each prerendered route and insert it into the Trie.
// Each route's concrete parameter values form a path in the Trie.
for (const route of prerenderedRoutes){
let currentNode = root // Start building the path from the root for each route.
;
// Iterate through the sorted parameter keys. The order of keys is crucial
// for ensuring that routes with the same concrete parameters follow the
// same path in the Trie, regardless of the original order of properties
// in the `params` object.
for (const { paramName: key } of pathnameSegments){
// Check if the current route actually has a concrete value for this parameter.
// If a dynamic segment is not filled (i.e., it's a fallback), it won't have
// this property, and we stop building the path for this route at this point.
if (route.params.hasOwnProperty(key)) {
const value = route.params[key];
// Generate a unique key for the parameter's value. This is critical
// to prevent collisions between different data types that might have
// the same string representation (e.g., `['a', 'b']` vs `'a,b'`).
// A type prefix (`A:` for Array, `S:` for String, `U:` for undefined)
// is added to the value to prevent collisions. This ensures that
// different types with the same string representation are treated as
// distinct.
let valueKey;
if (Array.isArray(value)) {
valueKey = `A:${value.join(',')}`;
} else if (value === undefined) {
valueKey = `U:undefined`;
} else {
valueKey = `S:${value}`;
}
// Look for a child node corresponding to this `valueKey` from the `currentNode`.
let childNode = currentNode.children.get(valueKey);
if (!childNode) {
// If the child node doesn't exist, create a new one and add it to
// the current node's children.
childNode = {
children: new Map(),
routes: []
};
currentNode.children.set(valueKey, childNode);
}
// Move deeper into the Trie to the `childNode` for the next parameter.
currentNode = childNode;
}
}
// After processing all concrete parameters for the route, add the full
// `PrerenderedRoute` object to the `routes` array of the `currentNode`.
// This node represents the unique concrete parameter combination for this route.
currentNode.routes.push(route);
}
// Phase 2: Traverse the Trie to assign the `throwOnEmptyStaticShell` property.
// This is done using an iterative Depth-First Search (DFS) approach with an
// explicit stack to avoid JavaScript's recursion depth limits (stack overflow)
// for very deep routing structures.
const stack = [
root
] // Initialize the stack with the root node.
;
while(stack.length > 0){
const node = stack.pop()// Pop the next node to process from the stack.
;
// `hasChildren` indicates if this node has any more specific concrete
// parameter combinations branching off from it. If true, it means this
// node represents a prefix for other, more specific routes.
const hasChildren = node.children.size > 0;
// If the current node has routes associated with it (meaning, routes whose
// concrete parameters lead to this node's path in the Trie).
if (node.routes.length > 0) {
// Determine the minimum number of fallback parameters among all routes
// that are associated with this current Trie node. This is used to
// identify if a route should not throw on empty static shell relative to another route *at the same level*
// of concrete parameters, but with fewer fallback parameters.
let minFallbacks = Infinity;
for (const r of node.routes){
// `fallbackRouteParams?.length ?? 0` handles cases where `fallbackRouteParams`
// might be `undefined` or `null`, treating them as 0 length.
minFallbacks = Math.min(minFallbacks, r.fallbackRouteParams ? r.fallbackRouteParams.length : 0);
}
// Now, for each `PrerenderedRoute` associated with this node:
for (const route of node.routes){
// A route is ok not to throw on an empty static shell (and thus
// `throwOnEmptyStaticShell` should be `false`) if either of the
// following conditions is met:
// 1. `hasChildren` is true: This node has further concrete parameter children.
// This means the current route is a parent to more specific routes (e.g.,
// `/blog/[slug]` should not throw when concrete routes like `/blog/first-post` exist).
// OR
// 2. `route.fallbackRouteParams.length > minFallbacks`: This route has
// more fallback parameters than another route at the same Trie node.
// This implies the current route is a more general version that should not throw
// compared to a more specific route that has fewer fallback parameters
// (e.g., `/1234/[...slug]` should not throw relative to `/[id]/[...slug]`).
if (hasChildren || route.fallbackRouteParams && route.fallbackRouteParams.length > minFallbacks) {
route.throwOnEmptyStaticShell = false // Should not throw on empty static shell.
;
} else {
route.throwOnEmptyStaticShell = true // Should throw on empty static shell.
;
}
if (computeRemainingPrerenderableParams && route.fallbackRouteParams && route.fallbackRouteParams.length > 0) {
const fallbackRouteParamsByName = new Map(route.fallbackRouteParams.map((param)=>[
param.paramName,
param
]));
const remainingPrerenderableParams = [];
// Only unresolved pathname params that can still be filled by
// generateStaticParams belong here. Once we hit an unresolved param
// that is purely dynamic, the rest of the shell also stays dynamic
// and cannot be completed into a more specific prerendered shell.
for (const segment of pathnameSegments){
if (route.params.hasOwnProperty(segment.paramName)) {
continue;
}
if (!segment.hasGenerateStaticParams) {
break;
}
const fallbackRouteParam = fallbackRouteParamsByName.get(segment.paramName);
if (!fallbackRouteParam) {
break;
}
remainingPrerenderableParams.push(fallbackRouteParam);
}
route.remainingPrerenderableParams = remainingPrerenderableParams.length > 0 ? remainingPrerenderableParams : undefined;
}
}
}
// Add all children of the current node to the stack. This ensures that
// the traversal continues to explore deeper paths in the Trie.
for (const child of node.children.values()){
stack.push(child);
}
}
}
/**
* Calls a single generateStaticParams function within a WorkUnitStore context,
* making root param getters available during static param generation.
*/ async function callGenerateStaticParams(generateStaticParams, workUnitAsyncStorage, parentParams, rootParamKeys, implicitTags) {
const rootParams = {};
for (const key of rootParamKeys){
if (key in parentParams) {
rootParams[key] = parentParams[key];
}
}
const workUnitStore = {
type: 'generate-static-params',
phase: 'render',
implicitTags,
rootParams
};
return workUnitAsyncStorage.run(workUnitStore, generateStaticParams, {
params: parentParams
});
}
/**
* Processes app directory segments to build route parameters from generateStaticParams functions.
* This function walks through the segments array and calls generateStaticParams for each segment that has it,
* combining parent parameters with child parameters to build the complete parameter combinations.
* Uses iterative processing instead of recursion for better performance.
*
* @param segments - Array of app directory segments to process
* @param store - Work store for tracking fetch cache configuration
* @param workUnitAsyncStorage - AsyncLocalStorage for work unit stores
* @param isRoutePPREnabled - Whether PPR is enabled for this route
* @param rootParamKeys - The keys identifying which params are root params
* @returns Promise that resolves to an array of all parameter combinations
*/ export async function generateRouteStaticParams(segments, store, workUnitAsyncStorage, isRoutePPREnabled, rootParamKeys) {
// Early return if no segments to process
if (segments.length === 0) return [];
const implicitTags = await getImplicitTags(store.page, store.page, null);
const queue = [
{
segmentIndex: 0,
params: []
}
];
let currentParams = [];
while(queue.length > 0){
var _current_config;
const { segmentIndex, params } = queue.shift();
// If we've processed all segments, this is our final result
if (segmentIndex >= segments.length) {
currentParams = params;
break;
}
const current = segments[segmentIndex];
// Skip segments without generateStaticParams and continue to next
if (typeof current.generateStaticParams !== 'function') {
queue.push({
segmentIndex: segmentIndex + 1,
params
});
continue;
}
// Configure fetchCache if specified
if (((_current_config = current.config) == null ? void 0 : _current_config.fetchCache) !== undefined) {
store.fetchCache = current.config.fetchCache;
}
const nextParams = [];
// If there are parent params, we need to process them.
if (params.length > 0) {
// Process each parent parameter combination
for (const parentParams of params){
const result = await callGenerateStaticParams(current.generateStaticParams, workUnitAsyncStorage, parentParams, rootParamKeys, implicitTags);
if (result.length > 0) {
// Merge parent params with each result item
for (const item of result){
nextParams.push({
...parentParams,
...item
});
}
} else if (isRoutePPREnabled) {
throwEmptyGenerateStaticParamsError();
} else {
// No results, just pass through parent params
nextParams.push(parentParams);
}
}
} else {
// No parent params, call generateStaticParams with empty object
const result = await callGenerateStaticParams(current.generateStaticParams, workUnitAsyncStorage, {}, rootParamKeys, implicitTags);
if (result.length === 0 && isRoutePPREnabled) {
throwEmptyGenerateStaticParamsError();
}
nextParams.push(...result);
}
// Add next segment to work queue
queue.push({
segmentIndex: segmentIndex + 1,
params: nextParams
});
}
return currentParams;
}
function createReplacements(segment, paramValue) {
// Determine the prefix to use for the interception marker.
let prefix;
if (segment.paramType) {
prefix = interceptionPrefixFromParamType(segment.paramType) ?? '';
} else {
prefix = '';
}
return {
pathname: prefix + encodeParam(paramValue, (value)=>// Only escape path delimiters if the value is a string, the following
// version will URL encode the value.
escapePathDelimiters(value, true)),
encodedPathname: prefix + encodeParam(paramValue, // URL encode the value.
encodeURIComponent)
};
}
/**
* Processes app directory segments to build route parameters from generateStaticParams functions.
* This function walks through the segments array and calls generateStaticParams for each segment that has it,
* combining parent parameters with child parameters to build the complete parameter combinations.
* Uses iterative processing instead of recursion for better performance.
*
* @param segments - Array of app directory segments to process
* @param store - Work store for tracking fetch cache configuration
* @returns Promise that resolves to an array of all parameter combinations
*/ export async function buildAppStaticPaths({ dir, page, route, distDir, cacheComponents, authInterrupts, segments, isrFlushToDisk, cacheHandler, cacheLifeProfiles, requestHeaders, cacheHandlers, cacheMaxMemorySize, fetchCacheKeyPrefix, nextConfigOutput, ComponentMod, isRoutePPREnabled = false, partialFallbacksEnabled = false, buildId, rootParamKeys }) {
if (segments.some((generate)=>{
var _generate_config;
return ((_generate_config = generate.config) == null ? void 0 : _generate_config.dynamicParams) === true;
}) && nextConfigOutput === 'export') {
throw Object.defineProperty(new Error('"dynamicParams: true" cannot be used with "output: export". See more info here: https://nextjs.org/docs/app/building-your-application/deploying/static-exports'), "__NEXT_ERROR_CODE", {
value: "E393",
enumerable: false,
configurable: true
});
}
ComponentMod.patchFetch();
const incrementalCache = await createIncrementalCache({
dir,
distDir,
cacheHandler,
cacheHandlers,
requestHeaders,
fetchCacheKeyPrefix,
flushToDisk: isrFlushToDisk,
cacheMaxMemorySize
});
// Extract segments that contribute to the pathname.
// For AppPageRouteModule: Traverses the loader tree to find all segments (including
// interception routes in parallel slots) that match the pathname
// For AppRouteRouteModule: Filters the segments array to get non-parallel route params
const pathnameRouteParamSegments = extractPathnameRouteParamSegments(ComponentMod.routeModule, segments, route);
const afterRunner = new AfterRunner();
const store = createWorkStore({
page,
renderOpts: {
incrementalCache,
cacheLifeProfiles,
supportsDynamicResponse: true,
cacheComponents,
experimental: {
authInterrupts
},
waitUntil: afterRunner.context.waitUntil,
onClose: afterRunner.context.onClose,
onAfterTaskError: afterRunner.context.onTaskError
},
buildId,
previouslyRevalidatedTags: []
});
const routeParams = await ComponentMod.workAsyncStorage.run(store, generateRouteStaticParams, segments, store, ComponentMod.workUnitAsyncStorage, isRoutePPREnabled, rootParamKeys);
const generatedParamNames = new Set();
for (const params of routeParams){
for (const paramName of Object.keys(params)){
generatedParamNames.add(paramName);
}
}
const prerenderablePathSegments = pathnameRouteParamSegments.map((segment)=>({
paramName: segment.paramName,
hasGenerateStaticParams: generatedParamNames.has(segment.paramName)
}));
await afterRunner.executeAfter();
let lastDynamicSegmentHadGenerateStaticParams = false;
for (const segment of segments){
var _segment_config;
// Check to see if there are any missing params for segments that have
// dynamicParams set to false.
if (segment.paramName && segment.paramType && ((_segment_config = segment.config) == null ? void 0 : _segment_config.dynamicParams) === false) {
for (const params of routeParams){
if (segment.paramName in params) continue;
const relative = segment.filePath ? path.relative(dir, segment.filePath) : undefined;
throw Object.defineProperty(new Error(`Segment "${relative}" exports "dynamicParams: false" but the param "${segment.paramName}" is missing from the generated route params.`), "__NEXT_ERROR_CODE", {
value: "E280",
enumerable: false,
configurable: true
});
}
}
if (segment.paramName && segment.paramType && typeof segment.generateStaticParams !== 'function') {
lastDynamicSegmentHadGenerateStaticParams = false;
} else if (typeof segment.generateStaticParams === 'function') {
lastDynamicSegmentHadGenerateStaticParams = true;
}
}
// Determine if all the segments have had their parameters provided.
const hadAllParamsGenerated = pathnameRouteParamSegments.length === 0 || routeParams.length > 0 && routeParams.every((params)=>{
for (const { paramName } of pathnameRouteParamSegments){
if (paramName in params) continue;
return false;
}
return true;
});
// TODO: dynamic params should be allowed to be granular per segment but
// we need additional information stored/leveraged in the prerender
// manifest to allow this behavior.
const dynamicParams = segments.every((segment)=>{
var _segment_config;
return ((_segment_config = segment.config) == null ? void 0 : _segment_config.dynamicParams) !== false;
});
const supportsRoutePreGeneration = hadAllParamsGenerated || !process.env.__NEXT_DEV_SERVER;
const fallbackMode = dynamicParams ? supportsRoutePreGeneration ? isRoutePPREnabled ? FallbackMode.PRERENDER : FallbackMode.BLOCKING_STATIC_RENDER : undefined : FallbackMode.NOT_FOUND;
const prerenderedRoutesByPathname = new Map();
// Convert rootParamKeys to Set for O(1) lookup.
const rootParamSet = new Set(rootParamKeys);
if (hadAllParamsGenerated || isRoutePPREnabled) {
let paramsToProcess = routeParams;
if (isRoutePPREnabled) {
// Discover all unique combinations of the routeParams so we can generate
// routes that won't throw on empty static shell for each of them if
// they're available.
paramsToProcess = generateAllParamCombinations(pathnameRouteParamSegments, routeParams, rootParamKeys);
// Collect all the fallback route params for the segments.
const fallbackRouteParams = [];
for (const segment of segments){
if (!segment.paramName || !segment.paramType) continue;
fallbackRouteParams.push({
paramName: segment.paramName,
paramType: segment.paramType
});
}
// Add the base route, this is the route with all the placeholders as it's
// derived from the `page` string.
prerenderedRoutesByPathname.set(page, {
params: {},
pathname: page,
encodedPathname: page,
fallbackRouteParams,
fallbackMode: calculateFallbackMode(dynamicParams, rootParamKeys, fallbackMode),
fallbackRootParams: rootParamKeys,
throwOnEmptyStaticShell: true
});
}
filterUniqueParams(pathnameRouteParamSegments, validateParams(page, isRoutePPREnabled, pathnameRouteParamSegments, rootParamKeys, paramsToProcess)).forEach((params)=>{
let pathname = page;
let encodedPathname = page;
const fallbackRouteParams = [];
for (const { name, paramName, paramType } of pathnameRouteParamSegments){
const paramValue = params[paramName];
if (!paramValue) {
if (isRoutePPREnabled) {
// Mark remaining params as fallback params.
fallbackRouteParams.push({
paramName,
paramType
});
for(let i = pathnameRouteParamSegments.findIndex((param)=>param.paramName === paramName) + 1; i < pathnameRouteParamSegments.length; i++){
fallbackRouteParams.push({
paramName: pathnameRouteParamSegments[i].paramName,
paramType: pathnameRouteParamSegments[i].paramType
});
}
break;
} else {
// This route is not complete, and we aren't performing a partial
// prerender, so we should return, skipping this route.
return;
}
}
const replacements = createReplacements({
paramType
}, paramValue);
pathname = pathname.replace(name, // We're replacing the segment name with the replacement pathname
// which will include the interception marker prefix if it exists.
replacements.pathname);
encodedPathname = encodedPathname.replace(name, // We're replacing the segment name with the replacement encoded
// pathname which will include the encoded param value.
replacements.encodedPathname);
}
// Resolve all route params from the loader tree if this is from an
// app page. This processes both regular route params and parallel route params.
if ('loaderTree' in ComponentMod.routeModule.userland && Array.isArray(ComponentMod.routeModule.userland.loaderTree)) {
resolveRouteParamsFromTree(ComponentMod.routeModule.userland.loaderTree, params, route, fallbackRouteParams);
}
const fallbackRootParams = [];
for (const { paramName } of fallbackRouteParams){
// If the param is a root param then we can add it to the fallback
// root params.
if (rootParamSet.has(paramName)) {
fallbackRootParams.push(paramName);
}
}
pathname = normalizePathname(pathname);
prerenderedRoutesByPathname.set(pathname, {
params,
pathname,
encodedPathname: normalizePathname(encodedPathname),
fallbackRouteParams,
fallbackMode: calculateFallbackMode(dynamicParams, fallbackRootParams, fallbackMode),
fallbackRootParams,
throwOnEmptyStaticShell: true
});
});
}
const prerenderedRoutes = prerenderedRoutesByPathname.size > 0 || lastDynamicSegmentHadGenerateStaticParams ? [
...prerenderedRoutesByPathname.values()
] : undefined;
// Now we have to set the throwOnEmptyStaticShell for each of the routes.
if (prerenderedRoutes && cacheComponents) {
assignStaticShellMetadata(prerenderedRoutes, prerenderablePathSegments, partialFallbacksEnabled);
}
return {
fallbackMode,
prerenderedRoutes
};
}
//# sourceMappingURL=app.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,134 @@
import { parseAppRouteSegment } from '../../../shared/lib/router/routes/app';
import { parseLoaderTree } from '../../../shared/lib/router/utils/parse-loader-tree';
import { resolveParamValue } from '../../../shared/lib/router/utils/resolve-param-value';
/**
* Validates that the static segments in currentPath match the corresponding
* segments in targetSegments. This ensures we only extract dynamic parameters
* that are part of the target pathname structure.
*
* Segments are compared literally - interception markers like "(.)photo" are
* part of the pathname and must match exactly.
*
* @example
* // Matching paths
* currentPath: ['blog', '(.)photo']
* targetSegments: ['blog', '(.)photo', '[id]']
* → Returns true (both static segments match exactly)
*
* @example
* // Non-matching paths
* currentPath: ['blog', '(.)photo']
* targetSegments: ['blog', 'photo', '[id]']
* → Returns false (segments don't match - marker is part of pathname)
*
* @param currentPath - The accumulated path segments from the loader tree
* @param targetSegments - The target pathname split into segments
* @returns true if all static segments match, false otherwise
*/ function validatePrefixMatch(currentPath, route) {
for(let i = 0; i < currentPath.length; i++){
const pathSegment = currentPath[i];
const targetPathSegment = route.segments[i];
// Type mismatch - one is static, one is dynamic
if (pathSegment.type !== targetPathSegment.type) {
return false;
}
// One has an interception marker, the other doesn't.
if (pathSegment.interceptionMarker !== targetPathSegment.interceptionMarker) {
return false;
}
// Both are static but names don't match
if (pathSegment.type === 'static' && targetPathSegment.type === 'static' && pathSegment.name !== targetPathSegment.name) {
return false;
} else if (pathSegment.type === 'dynamic' && targetPathSegment.type === 'dynamic' && pathSegment.param.paramType !== targetPathSegment.param.paramType && pathSegment.param.paramName !== targetPathSegment.param.paramName) {
return false;
}
}
return true;
}
/**
* Extracts pathname route param segments from a loader tree and resolves
* parameter values from static segments in the route.
*
* @param loaderTree - The loader tree structure containing route hierarchy
* @param route - The target route to match against
* @returns Object containing pathname route param segments and resolved params
*/ export function extractPathnameRouteParamSegmentsFromLoaderTree(loaderTree, route) {
const pathnameRouteParamSegments = [];
const params = {};
// BFS traversal with depth and path tracking
const queue = [
{
tree: loaderTree,
depth: 0,
currentPath: []
}
];
while(queue.length > 0){
const { tree, depth, currentPath } = queue.shift();
const { segment, parallelRoutes } = parseLoaderTree(tree);
// Build the path for the current node
let updatedPath = currentPath;
let nextDepth = depth;
const appSegment = parseAppRouteSegment(segment);
// Only add to path if it's a real segment that appears in the URL
// Route groups and parallel markers don't contribute to URL pathname
if (appSegment && appSegment.type !== 'route-group' && appSegment.type !== 'parallel-route') {
updatedPath = [
...currentPath,
appSegment
];
nextDepth = depth + 1;
}
// Check if this segment has a param and matches the target pathname at this depth
if ((appSegment == null ? void 0 : appSegment.type) === 'dynamic') {
const { paramName, paramType } = appSegment.param;
// Check if this segment is at the correct depth in the target pathname
// A segment matches if:
// 1. There's a dynamic segment at this depth in the pathname
// 2. The parameter names match (e.g., [id] matches [id], not [category])
// 3. The static segments leading up to this point match (prefix check)
if (depth < route.segments.length) {
const targetSegment = route.segments[depth];
// Match if the target pathname has a dynamic segment at this depth
if (targetSegment.type === 'dynamic') {
// Check that parameter names match exactly
// This prevents [category] from matching against /[id]
if (paramName !== targetSegment.param.paramName) {
continue; // Different param names, skip this segment
}
// Validate that the path leading up to this dynamic segment matches
// the target pathname. This prevents false matches like extracting
// [slug] from "/news/[slug]" when the tree has "/blog/[slug]"
if (validatePrefixMatch(currentPath, route)) {
pathnameRouteParamSegments.push({
name: segment,
paramName,
paramType
});
}
}
}
// Resolve parameter value if it's not already known.
if (!params.hasOwnProperty(paramName)) {
const paramValue = resolveParamValue(paramName, paramType, depth, route, params);
if (paramValue !== undefined) {
params[paramName] = paramValue;
}
}
}
// Continue traversing all parallel routes to find matching segments
for (const parallelRoute of Object.values(parallelRoutes)){
queue.push({
tree: parallelRoute,
depth: nextDepth,
currentPath: updatedPath
});
}
}
return {
pathnameRouteParamSegments,
params
};
}
//# sourceMappingURL=extract-pathname-route-param-segments-from-loader-tree.js.map
File diff suppressed because one or more lines are too long
+154
View File
@@ -0,0 +1,154 @@
import { normalizeLocalePath } from '../../shared/lib/i18n/normalize-locale-path';
import { parseStaticPathsResult } from '../../lib/fallback';
import escapePathDelimiters from '../../shared/lib/router/utils/escape-path-delimiters';
import { removeTrailingSlash } from '../../shared/lib/router/utils/remove-trailing-slash';
import { getRouteMatcher } from '../../shared/lib/router/utils/route-matcher';
import { getRouteRegex } from '../../shared/lib/router/utils/route-regex';
import { encodeParam, normalizePathname } from './utils';
export async function buildPagesStaticPaths({ page, getStaticPaths, configFileName, locales, defaultLocale }) {
const prerenderedRoutesByPathname = new Map();
const _routeRegex = getRouteRegex(page);
const _routeMatcher = getRouteMatcher(_routeRegex);
// Get the default list of allowed params.
const routeParameterKeys = Object.keys(_routeMatcher(page));
const staticPathsResult = await getStaticPaths({
// We create a copy here to avoid having the types of `getStaticPaths`
// change. This ensures that users can't mutate this array and have it
// poison the reference.
locales: [
...locales ?? []
],
defaultLocale
});
const expectedReturnVal = `Expected: { paths: [], fallback: boolean }\n` + `See here for more info: https://nextjs.org/docs/messages/invalid-getstaticpaths-value`;
if (!staticPathsResult || typeof staticPathsResult !== 'object' || Array.isArray(staticPathsResult)) {
throw Object.defineProperty(new Error(`Invalid value returned from getStaticPaths in ${page}. Received ${typeof staticPathsResult} ${expectedReturnVal}`), "__NEXT_ERROR_CODE", {
value: "E1004",
enumerable: false,
configurable: true
});
}
const invalidStaticPathKeys = Object.keys(staticPathsResult).filter((key)=>!(key === 'paths' || key === 'fallback'));
if (invalidStaticPathKeys.length > 0) {
throw Object.defineProperty(new Error(`Extra keys returned from getStaticPaths in ${page} (${invalidStaticPathKeys.join(', ')}) ${expectedReturnVal}`), "__NEXT_ERROR_CODE", {
value: "E1047",
enumerable: false,
configurable: true
});
}
if (!(typeof staticPathsResult.fallback === 'boolean' || staticPathsResult.fallback === 'blocking')) {
throw Object.defineProperty(new Error(`The \`fallback\` key must be returned from getStaticPaths in ${page}.\n` + expectedReturnVal), "__NEXT_ERROR_CODE", {
value: "E1034",
enumerable: false,
configurable: true
});
}
const toPrerender = staticPathsResult.paths;
if (!Array.isArray(toPrerender)) {
throw Object.defineProperty(new Error(`Invalid \`paths\` value returned from getStaticPaths in ${page}.\n` + `\`paths\` must be an array of strings or objects of shape { params: [key: string]: string }`), "__NEXT_ERROR_CODE", {
value: "E83",
enumerable: false,
configurable: true
});
}
toPrerender.forEach((entry)=>{
// For a string-provided path, we must make sure it matches the dynamic
// route.
if (typeof entry === 'string') {
entry = removeTrailingSlash(entry);
const localePathResult = normalizeLocalePath(entry, locales);
let cleanedEntry = entry;
if (localePathResult.detectedLocale) {
cleanedEntry = entry.slice(localePathResult.detectedLocale.length + 1);
} else if (defaultLocale) {
entry = `/${defaultLocale}${entry}`;
}
const params = _routeMatcher(cleanedEntry);
if (!params) {
throw Object.defineProperty(new Error(`The provided path \`${cleanedEntry}\` does not match the page: \`${page}\`.`), "__NEXT_ERROR_CODE", {
value: "E481",
enumerable: false,
configurable: true
});
}
// If leveraging the string paths variant the entry should already be
// encoded so we decode the segments ensuring we only escape path
// delimiters
const pathname = entry.split('/').map((segment)=>escapePathDelimiters(decodeURIComponent(segment), true)).join('/');
if (!prerenderedRoutesByPathname.has(pathname)) {
prerenderedRoutesByPathname.set(pathname, {
params,
pathname,
encodedPathname: entry,
fallbackRouteParams: undefined,
fallbackMode: parseStaticPathsResult(staticPathsResult.fallback),
fallbackRootParams: undefined,
throwOnEmptyStaticShell: undefined
});
}
} else {
const invalidKeys = Object.keys(entry).filter((key)=>key !== 'params' && key !== 'locale');
if (invalidKeys.length) {
throw Object.defineProperty(new Error(`Additional keys were returned from \`getStaticPaths\` in page "${page}". ` + `URL Parameters intended for this dynamic route must be nested under the \`params\` key, i.e.:` + `\n\n\treturn { params: { ${routeParameterKeys.map((k)=>`${k}: ...`).join(', ')} } }` + `\n\nKeys that need to be moved: ${invalidKeys.join(', ')}.\n`), "__NEXT_ERROR_CODE", {
value: "E322",
enumerable: false,
configurable: true
});
}
const { params = {} } = entry;
let builtPage = page;
let encodedBuiltPage = page;
routeParameterKeys.forEach((validParamKey)=>{
const { repeat, optional } = _routeRegex.groups[validParamKey];
let paramValue = params[validParamKey];
if (optional && params.hasOwnProperty(validParamKey) && (paramValue === null || paramValue === undefined || paramValue === false)) {
paramValue = [];
}
if (repeat && !Array.isArray(paramValue) || !repeat && typeof paramValue !== 'string' || typeof paramValue === 'undefined') {
throw Object.defineProperty(new Error(`A required parameter (${validParamKey}) was not provided as ${repeat ? 'an array' : 'a string'} received ${typeof paramValue} in getStaticPaths for ${page}`), "__NEXT_ERROR_CODE", {
value: "E620",
enumerable: false,
configurable: true
});
}
let replaced = `[${repeat ? '...' : ''}${validParamKey}]`;
if (optional) {
replaced = `[${replaced}]`;
}
builtPage = builtPage.replace(replaced, encodeParam(paramValue, (value)=>escapePathDelimiters(value, true)));
encodedBuiltPage = encodedBuiltPage.replace(replaced, encodeParam(paramValue, encodeURIComponent));
});
if (!builtPage && !encodedBuiltPage) {
return;
}
if (entry.locale && !(locales == null ? void 0 : locales.includes(entry.locale))) {
throw Object.defineProperty(new Error(`Invalid locale returned from getStaticPaths for ${page}, the locale ${entry.locale} is not specified in ${configFileName}`), "__NEXT_ERROR_CODE", {
value: "E358",
enumerable: false,
configurable: true
});
}
const curLocale = entry.locale || defaultLocale || '';
const pathname = normalizePathname(`${curLocale ? `/${curLocale}` : ''}${curLocale && builtPage === '/' ? '' : builtPage}`);
if (!prerenderedRoutesByPathname.has(pathname)) {
prerenderedRoutesByPathname.set(pathname, {
params,
pathname,
encodedPathname: normalizePathname(`${curLocale ? `/${curLocale}` : ''}${curLocale && encodedBuiltPage === '/' ? '' : encodedBuiltPage}`),
fallbackRouteParams: undefined,
fallbackMode: parseStaticPathsResult(staticPathsResult.fallback),
fallbackRootParams: undefined,
throwOnEmptyStaticShell: undefined
});
}
}
});
return {
fallbackMode: parseStaticPathsResult(staticPathsResult.fallback),
prerenderedRoutes: [
...prerenderedRoutesByPathname.values()
]
};
}
//# sourceMappingURL=pages.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
export { };
//# sourceMappingURL=types.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/build/static-paths/types.ts"],"sourcesContent":["import type { FallbackMode } from '../../lib/fallback'\nimport type { Params } from '../../server/request/params'\nimport type { DynamicParamTypes } from '../../shared/lib/app-router-types'\n\ntype StaticPrerenderedRoute = {\n readonly params: Params\n readonly pathname: string\n readonly encodedPathname: string\n readonly fallbackRouteParams: undefined\n readonly fallbackMode: FallbackMode | undefined\n readonly fallbackRootParams: undefined\n remainingPrerenderableParams?: undefined\n\n /**\n * When enabled, the route will be rendered with diagnostics enabled which\n * will error the build if the route that is generated is empty.\n */\n throwOnEmptyStaticShell: undefined\n}\n\nexport type FallbackRouteParam = {\n /**\n * The name of the param.\n */\n readonly paramName: string\n\n /**\n * The type of the param.\n */\n readonly paramType: DynamicParamTypes\n}\n\ntype FallbackPrerenderedRoute = {\n readonly params: Params\n readonly pathname: string\n readonly encodedPathname: string\n\n /**\n * The fallback route params for the route. This includes all route parameters\n * that are unknown at build time, from both the main children route and any\n * parallel routes.\n */\n readonly fallbackRouteParams: readonly FallbackRouteParam[]\n readonly fallbackMode: FallbackMode | undefined\n readonly fallbackRootParams: readonly string[]\n remainingPrerenderableParams?: readonly FallbackRouteParam[]\n\n /**\n * When enabled, the route will be rendered with diagnostics enabled which\n * will error the build if the route that is generated is empty.\n */\n throwOnEmptyStaticShell: boolean\n}\n\nexport type PrerenderedRoute = StaticPrerenderedRoute | FallbackPrerenderedRoute\n\nexport type StaticPathsResult = {\n fallbackMode: FallbackMode | undefined\n prerenderedRoutes: PrerenderedRoute[] | undefined\n}\n"],"names":[],"mappings":"AAwDA,WAGC","ignoreList":[0]}
+121
View File
@@ -0,0 +1,121 @@
import { isAppPageRouteModule } from '../../server/route-modules/checks';
import { parseAppRouteSegment } from '../../shared/lib/router/routes/app';
import { parseLoaderTree } from '../../shared/lib/router/utils/parse-loader-tree';
import { extractPathnameRouteParamSegmentsFromLoaderTree } from './app/extract-pathname-route-param-segments-from-loader-tree';
import { resolveParamValue } from '../../shared/lib/router/utils/resolve-param-value';
/**
* Encodes a parameter value using the provided encoder.
*
* @param value - The value to encode.
* @param encoder - The encoder to use.
* @returns The encoded value.
*/ export function encodeParam(value, encoder) {
let replaceValue;
if (Array.isArray(value)) {
replaceValue = value.map(encoder).join('/');
} else {
replaceValue = encoder(value);
}
return replaceValue;
}
/**
* Normalizes a pathname to a consistent format.
*
* @param pathname - The pathname to normalize.
* @returns The normalized pathname.
*/ export function normalizePathname(pathname) {
return pathname.replace(/\\/g, '/').replace(/(?!^)\/$/, '');
}
/**
* Extracts segments that contribute to the pathname by traversing the loader tree
* based on the route module type.
*
* @param routeModule - The app route module (page or route handler)
* @param segments - Array of AppSegment objects collected from the route
* @param page - The target pathname to match against, INCLUDING interception
* markers (e.g., "/blog/[slug]", "/(.)photo/[id]")
* @returns Array of segments with param info that contribute to the pathname
*/ export function extractPathnameRouteParamSegments(routeModule, segments, route) {
// For AppPageRouteModule, use the loaderTree traversal approach
if (isAppPageRouteModule(routeModule)) {
const { pathnameRouteParamSegments } = extractPathnameRouteParamSegmentsFromLoaderTree(routeModule.userland.loaderTree, route);
return pathnameRouteParamSegments;
}
return extractPathnameRouteParamSegmentsFromSegments(segments);
}
export function extractPathnameRouteParamSegmentsFromSegments(segments) {
// TODO: should we consider what values are already present in the page?
// For AppRouteRouteModule, filter the segments array to get the route params
// that contribute to the pathname.
const result = [];
for (const segment of segments){
// Skip segments without param info.
if (!segment.paramName || !segment.paramType) continue;
// Collect all the route param keys that contribute to the pathname.
result.push({
name: segment.name,
paramName: segment.paramName,
paramType: segment.paramType
});
}
return result;
}
/**
* Resolves all route parameters from the loader tree. This function uses
* tree-based traversal to correctly handle the hierarchical structure of routes
* and accurately determine parameter values based on their depth in the tree.
*
* This processes both regular route parameters (from the main children route) and
* parallel route parameters (from slots like @modal, @sidebar).
*
* Unlike interpolateParallelRouteParams (which has a complete URL at runtime),
* this build-time function determines which route params are unknown.
* The pathname may contain placeholders like [slug], making it incomplete.
*
* @param loaderTree - The loader tree structure containing route hierarchy
* @param params - The current route parameters object (will be mutated)
* @param route - The current route being processed
* @param fallbackRouteParams - Array of fallback route parameters (will be mutated)
*/ export function resolveRouteParamsFromTree(loaderTree, params, route, fallbackRouteParams) {
// Stack-based traversal with depth tracking
const stack = [
{
tree: loaderTree,
depth: 0
}
];
while(stack.length > 0){
const { tree, depth } = stack.pop();
const { segment, parallelRoutes } = parseLoaderTree(tree);
const appSegment = parseAppRouteSegment(segment);
// If this segment is a route parameter, then we should process it if it's
// not already known and is not already marked as a fallback route param.
if ((appSegment == null ? void 0 : appSegment.type) === 'dynamic' && !params.hasOwnProperty(appSegment.param.paramName) && !fallbackRouteParams.some((param)=>param.paramName === appSegment.param.paramName)) {
const { paramName, paramType } = appSegment.param;
const paramValue = resolveParamValue(paramName, paramType, depth, route, params);
if (paramValue !== undefined) {
params[paramName] = paramValue;
} else if (paramType !== 'optional-catchall') {
// If we couldn't resolve the param, mark it as a fallback
fallbackRouteParams.push({
paramName,
paramType
});
}
}
// Calculate next depth - increment if this is not a route group and not empty
let nextDepth = depth;
if (appSegment && appSegment.type !== 'route-group' && appSegment.type !== 'parallel-route') {
nextDepth++;
}
// Add all parallel routes to the stack for processing.
for (const parallelRoute of Object.values(parallelRoutes)){
stack.push({
tree: parallelRoute,
depth: nextDepth
});
}
}
}
//# sourceMappingURL=utils.js.map
File diff suppressed because one or more lines are too long