import { AnyZodObject, z } from 'zod' import { 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: any) => Promise ? T : unknown export interface CreatePageProps< Params extends readonly string[] | AnyZodObject, SearchParams extends readonly string[] | AnyZodObject, Loader extends LoaderFn = LoaderFn< Params, SearchParams >, > { 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< Params, SearchParams >, >( 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: any) { const params = parseParams(props.params, paramsSchema) const searchParams = parseParams(props.searchParams, searchParamsSchema) let pageProps: any = { params, searchParams, } if (typeof loader === 'function') { const data = await loader(pageProps) pageProps = { ...pageProps, data, } } 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 return metadata( { params, searchParams, data, }, parent, ) }, Page, } } return { metadata, Page, } }