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 = < Params extends readonly string[] | AnyZodObject, SearchParams extends readonly string[] | AnyZodObject, Loader extends LoaderFn = LoaderFn, >( props: CreatePageProps, ) => { const { params: paramsSchema, searchParams: searchParamsSchema, component: PageComponent, loader, metadata } = props; async function Page(props: { params: Record; searchParams: Record; }) { const params = parseParams(props.params, paramsSchema); const searchParams = parseParams(props.searchParams, searchParamsSchema); const pageProps = { data: undefined as InferLoaderData, params, searchParams, }; 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, }; };