import type { AnyZodObject, z } from 'zod' import type { Metadata, ResolvingMetadata } from 'next' type InferParams = Params extends readonly string[] ? { [K in Params[number]]?: string } : Params extends AnyZodObject ? z.infer : unknown type LoaderFn< Params extends readonly string[] | AnyZodObject, SearchParams extends readonly string[] | AnyZodObject > = (args: { params: InferParams searchParams: InferParams }) => Promise type InferLoaderData = Loader extends (args: unknown) => Promise ? T : unknown export interface CreatePageProps< Params extends readonly string[] | AnyZodObject, SearchParams extends readonly string[] | AnyZodObject, Loader extends LoaderFn = LoaderFn > { params?: Params searchParams?: SearchParams loader?: Loader metadata?: | Metadata | (( args: { params: InferParams searchParams: InferParams data: InferLoaderData }, parent: ResolvingMetadata ) => Promise) component: React.ComponentType<{ params: InferParams searchParams?: InferParams data: InferLoaderData }> } function parseParams( params: Record, schema?: Schema ) { if (schema && 'parse' in schema) { return schema.parse(params) as InferParams } return params as InferParams } export const createPage = < const Params extends readonly string[] | AnyZodObject, const SearchParams extends readonly string[] | AnyZodObject, Loader extends LoaderFn = LoaderFn >( props: CreatePageProps ) => { const { params: paramsSchema, searchParams: searchParamsSchema, component: PageComponent, loader, metadata, } = props // We don't really care about the types here since it's internal async function Page(props: { params: Record searchParams: Record }) { const params = parseParams(props.params, paramsSchema) const searchParams = parseParams(props.searchParams, searchParamsSchema) const pageProps: { params: InferParams searchParams: InferParams data: InferLoaderData } = { params, searchParams, } as never if (typeof loader === 'function') { pageProps.data = (await loader(pageProps)) as InferLoaderData } return } if (typeof metadata === 'function') { return { generateMetaData: async ( { params, searchParams, }: { params: InferParams searchParams: InferParams }, parent: ResolvingMetadata ) => { const data = ( typeof loader === 'function' ? await loader({ params, searchParams, }) : undefined ) as InferLoaderData return metadata( { params, searchParams, data, }, parent ) }, Page, } } return { metadata, Page, } }