import { resolveRouteParamsFromTree } from '../../build/static-paths/utils' import type { FallbackRouteParam } from '../../build/static-paths/types' import type { DynamicParamTypesShort } from '../../shared/lib/app-router-types' import { dynamicParamTypes } from '../app-render/get-short-dynamic-param-type' import type AppPageRouteModule from '../route-modules/app-page/module' import { parseAppRoute } from '../../shared/lib/router/routes/app' import { extractPathnameRouteParamSegmentsFromLoaderTree } from '../../build/static-paths/app/extract-pathname-route-param-segments-from-loader-tree' export type OpaqueFallbackRouteParamValue = [ /** * The search value of the fallback route param. This is the opaque key * that will be used to replace the dynamic param in the postponed state. */ searchValue: string, /** * The dynamic param type of the fallback route param. This is the type of * the dynamic param that will be used to replace the dynamic param in the * postponed state. */ dynamicParamType: DynamicParamTypesShort, ] /** * An opaque fallback route params object. This is used to store the fallback * route params in a way that is not easily accessible to the client. */ export type OpaqueFallbackRouteParams = ReadonlyMap< string, OpaqueFallbackRouteParamValue > /** * The entries of the opaque fallback route params object. * * @param key the key of the fallback route param * @param value the value of the fallback route param */ export type OpaqueFallbackRouteParamEntries = ReturnType extends MapIterator< [infer K, infer V] > ? ReadonlyArray<[K, V]> : never /** * Creates an opaque fallback route params object from the fallback route params. * * @param fallbackRouteParams the fallback route params * @returns the opaque fallback route params */ export function createOpaqueFallbackRouteParams( fallbackRouteParams: readonly FallbackRouteParam[] ): OpaqueFallbackRouteParams | null { // If there are no fallback route params, we can return early. if (fallbackRouteParams.length === 0) return null // As we're creating unique keys for each of the dynamic route params, we only // need to generate a unique ID once per request because each of the keys will // be also be unique. const uniqueID = Math.random().toString(16).slice(2) const keys = new Map() // Generate a unique key for the fallback route param, if this key is found // in the static output, it represents a bug in cache components. for (const { paramName, paramType } of fallbackRouteParams) { keys.set(paramName, [ `%%drp:${paramName}:${uniqueID}%%`, dynamicParamTypes[paramType], ]) } return keys } /** * Gets the fallback route params for a given page. This is an expensive * operation because it requires parsing the loader tree to extract the fallback * route params. * * @param page the page * @param routeModule the route module * @returns the opaque fallback route params */ export function getFallbackRouteParams( page: string, routeModule: AppPageRouteModule ) { const route = parseAppRoute(page, true) // Extract the pathname-contributing segments from the loader tree. This // mirrors the logic in buildAppStaticPaths where we determine which segments // actually contribute to the pathname. const { pathnameRouteParamSegments, params } = extractPathnameRouteParamSegmentsFromLoaderTree( routeModule.userland.loaderTree, route ) // Create fallback route params for the pathname segments. const fallbackRouteParams: FallbackRouteParam[] = pathnameRouteParamSegments.map(({ paramName, paramType }) => ({ paramName, paramType, })) // Resolve route params from the loader tree. This mutates the // fallbackRouteParams array to add any route params that are // unknown at request time. // // The page parameter contains placeholders like [slug], which helps // resolveRouteParamsFromTree determine which params are unknown. resolveRouteParamsFromTree( routeModule.userland.loaderTree, params, // Static params extracted from the page route, // The page pattern with placeholders fallbackRouteParams // Will be mutated to add route params ) // Convert the fallback route params to an opaque format that can be safely // used in the postponed state without exposing implementation details. return createOpaqueFallbackRouteParams(fallbackRouteParams) }