Skip to content

Instantly share code, notes, and snippets.

@magicspon
Forked from Pagebakers/create-page.tsx
Last active February 18, 2025 19:38
Show Gist options
  • Save magicspon/c1647cf91909808be0a1eed5448bb56d to your computer and use it in GitHub Desktop.
Save magicspon/c1647cf91909808be0a1eed5448bb56d to your computer and use it in GitHub Desktop.

Revisions

  1. magicspon revised this gist Feb 18, 2025. 1 changed file with 14 additions and 9 deletions.
    23 changes: 14 additions & 9 deletions create-page.tsx
    Original file line number Diff line number Diff line change
    @@ -25,7 +25,7 @@ type LoaderFn<
    }) => Promise<any>

    type CacheArgs = {
    enabled: boolean
    enabled?: boolean
    revalidate?: number | false | undefined
    tags?: string[] | undefined
    keyParts?: string[]
    @@ -76,6 +76,7 @@ export interface CreatePageProps<
    params: InferParams<Params>
    searchParams: InferParams<SearchParams>
    data: InferLoaderData<Loader>
    draftMode: DraftMode
    },
    parent: ResolvingMetadata,
    ) => Promise<Metadata>)
    @@ -130,14 +131,17 @@ export const createPage = <
    params: InferParams<Params>
    searchParams: InferParams<SearchParams>
    }) => {
    if (!loader) return {}
    if (!loader)
    return {
    draftMode: { isEnabled: false },
    }

    const { isEnabled } = allowDraft ? await draftMode() : { isEnabled: false }
    const previewMode = { isEnabled }
    const props = { ...pageProps, draftMode: previewMode }
    const props = { ...pageProps, draftMode: { isEnabled } }

    const {
    enabled,
    enabled = true,
    revalidate = DEFAULT_REVALIDATE_TIME,
    tags = undefined,
    keyParts = undefined,
    @@ -156,7 +160,7 @@ export const createPage = <

    const data = await fn()

    return { previewMode, data }
    return { draftMode: { isEnabled }, data }
    }

    // We don't really care about the types here since it's internal
    @@ -174,12 +178,12 @@ export const createPage = <
    }

    if (typeof loader === 'function') {
    const { data, previewMode } = await runLoader(pageProps)
    const { data, draftMode } = await runLoader(pageProps)

    pageProps = {
    ...pageProps,
    data,
    draftMode: previewMode,
    draftMode,
    }
    }

    @@ -204,19 +208,20 @@ export const createPage = <
    searchParamsSchema,
    )

    const { data } =
    const { data, draftMode } =
    typeof loader === 'function'
    ? await runLoader({
    params: _params,
    searchParams: _searchParams,
    })
    : {}
    : { draftMode: { isEnabled: false } }

    return metadata(
    {
    params: _params,
    searchParams: _searchParams,
    data,
    draftMode,
    },
    parent,
    )
  2. magicspon revised this gist Feb 18, 2025. 1 changed file with 89 additions and 9 deletions.
    98 changes: 89 additions & 9 deletions create-page.tsx
    Original file line number Diff line number Diff line change
    @@ -1,8 +1,13 @@
    // https://saas-ui.dev/blog/nextjs-create-page-helper-with-loader-pattern
    // source: https://gist.github.com/magicspon/c1647cf91909808be0a1eed5448bb56d
    // updated by @magicspon
    import type { Metadata, ResolvingMetadata } from 'next'
    import { unstable_cache as cache } from 'next/cache'
    import { draftMode } from 'next/headers'
    import type { AnyZodObject, z } from 'zod'

    const DEFAULT_REVALIDATE_TIME = 60 * 60 * 24 // 1 DAY

    type InferParams<Params> = Params extends readonly string[]
    ? Record<Params[number], string>
    : Params extends AnyZodObject
    @@ -12,26 +17,58 @@ type InferParams<Params> = Params extends readonly string[]
    type LoaderFn<
    Params extends readonly string[] | AnyZodObject,
    SearchParams extends readonly string[] | AnyZodObject,
    DraftMode,
    > = (args: {
    params: InferParams<Params>
    searchParams: InferParams<SearchParams>
    draftMode: DraftMode
    }) => Promise<any>

    type CacheArgs = {
    enabled: boolean
    revalidate?: number | false | undefined
    tags?: string[] | undefined
    keyParts?: string[]
    }

    type ShouldCacheFn<
    Params extends readonly string[] | AnyZodObject,
    SearchParams extends readonly string[] | AnyZodObject,
    DraftMode,
    > = (args: {
    params: InferParams<Params>
    searchParams: InferParams<SearchParams>
    draftMode: DraftMode
    }) => Promise<CacheArgs>

    type InferLoaderData<Loader> = Loader extends (args: any) => Promise<infer T>
    ? T
    : unknown

    type DraftMode = {
    isEnabled: boolean
    }

    export interface CreatePageProps<
    Params extends readonly string[] | AnyZodObject,
    SearchParams extends readonly string[] | AnyZodObject,
    Loader extends LoaderFn<Params, SearchParams> = LoaderFn<
    Loader extends LoaderFn<Params, SearchParams, DraftMode> = LoaderFn<
    Params,
    SearchParams
    SearchParams,
    DraftMode
    >,
    ShouldCache extends ShouldCacheFn<
    Params,
    SearchParams,
    DraftMode
    > = ShouldCacheFn<Params, SearchParams, DraftMode>,
    > {
    params?: Params
    searchParams?: SearchParams
    loader?: Loader
    caching?: ShouldCache
    allowDraft?: boolean

    metadata?:
    | Metadata
    | ((
    @@ -46,6 +83,7 @@ export interface CreatePageProps<
    params: InferParams<Params>
    searchParams?: InferParams<SearchParams>
    data: InferLoaderData<Loader>
    draftMode: DraftMode
    }>
    }

    @@ -65,21 +103,62 @@ async function parseParams<Schema extends readonly string[] | AnyZodObject>(
    export const createPage = <
    const Params extends readonly string[] | AnyZodObject,
    const SearchParams extends readonly string[] | AnyZodObject,
    Loader extends LoaderFn<Params, SearchParams> = LoaderFn<
    Loader extends LoaderFn<Params, SearchParams, DraftMode> = LoaderFn<
    Params,
    SearchParams
    SearchParams,
    DraftMode
    >,
    ShouldCache extends ShouldCacheFn<
    Params,
    SearchParams,
    DraftMode
    > = ShouldCacheFn<Params, SearchParams, DraftMode>,
    >(
    props: CreatePageProps<Params, SearchParams, Loader>,
    props: CreatePageProps<Params, SearchParams, Loader, ShouldCache>,
    ) => {
    const {
    params: paramsSchema,
    searchParams: searchParamsSchema,
    component: PageComponent,
    loader,
    metadata,
    caching = () => Promise.resolve({ enabled: false } as CacheArgs),
    allowDraft = true,
    } = props

    const runLoader = async (pageProps: {
    params: InferParams<Params>
    searchParams: InferParams<SearchParams>
    }) => {
    if (!loader) return {}

    const { isEnabled } = allowDraft ? await draftMode() : { isEnabled: false }
    const previewMode = { isEnabled }
    const props = { ...pageProps, draftMode: previewMode }

    const {
    enabled,
    revalidate = DEFAULT_REVALIDATE_TIME,
    tags = undefined,
    keyParts = undefined,
    } = await caching(props)
    if (enabled) {
    console.info(`Using cached loader`)
    }

    const fn =
    !enabled || previewMode.isEnabled
    ? loader.bind(null, props)
    : cache(async () => loader(props), keyParts, {
    revalidate,
    tags,
    })

    const data = await fn()

    return { previewMode, data }
    }

    // We don't really care about the types here since it's internal

    async function Page(props: any) {
    @@ -95,11 +174,12 @@ export const createPage = <
    }

    if (typeof loader === 'function') {
    const data = await loader(pageProps)
    const { data, previewMode } = await runLoader(pageProps)

    pageProps = {
    ...pageProps,
    data,
    draftMode: previewMode,
    }
    }

    @@ -124,13 +204,13 @@ export const createPage = <
    searchParamsSchema,
    )

    const data =
    const { data } =
    typeof loader === 'function'
    ? await loader({
    ? await runLoader({
    params: _params,
    searchParams: _searchParams,
    })
    : undefined
    : {}

    return metadata(
    {
  3. magicspon revised this gist Feb 5, 2025. 1 changed file with 72 additions and 59 deletions.
    131 changes: 72 additions & 59 deletions create-page.tsx
    Original file line number Diff line number Diff line change
    @@ -1,139 +1,152 @@
    /* eslint-disable @typescript-eslint/no-explicit-any */
    import { AnyZodObject, z } from "zod";
    import { Metadata, ResolvingMetadata } from "next";
    // https://saas-ui.dev/blog/nextjs-create-page-helper-with-loader-pattern
    // source: https://gist.github.com/magicspon/c1647cf91909808be0a1eed5448bb56d
    import type { Metadata, ResolvingMetadata } from 'next'
    import type { AnyZodObject, z } from 'zod'

    type InferParams<Params> = Params extends readonly string[]
    ? {
    [K in Params[number]]: string;
    }
    ? Record<Params[number], string>
    : Params extends AnyZodObject
    ? z.infer<Params>
    : unknown;
    ? z.infer<Params>
    : unknown

    type LoaderFn<
    Params extends readonly string[] | AnyZodObject,
    SearchParams extends readonly string[] | AnyZodObject
    SearchParams extends readonly string[] | AnyZodObject,
    > = (args: {
    params: InferParams<Params>;
    searchParams: InferParams<SearchParams>;
    }) => Promise<any>;
    params: InferParams<Params>
    searchParams: InferParams<SearchParams>
    }) => Promise<any>

    type InferLoaderData<Loader> = Loader extends (args: any) => Promise<infer T>
    ? T
    : unknown;
    : unknown

    export interface CreatePageProps<
    Params extends readonly string[] | AnyZodObject,
    SearchParams extends readonly string[] | AnyZodObject,
    Loader extends LoaderFn<Params, SearchParams> = LoaderFn<Params, SearchParams>
    Loader extends LoaderFn<Params, SearchParams> = LoaderFn<
    Params,
    SearchParams
    >,
    > {
    params?: Params;
    searchParams?: SearchParams;
    loader?: Loader;
    params?: Params
    searchParams?: SearchParams
    loader?: Loader
    metadata?:
    | Metadata
    | ((
    args: {
    params: InferParams<Params>;
    searchParams: InferParams<SearchParams>;
    data: InferLoaderData<Loader>;
    params: InferParams<Params>
    searchParams: InferParams<SearchParams>
    data: InferLoaderData<Loader>
    },
    parent: ResolvingMetadata
    ) => Promise<Metadata>);
    parent: ResolvingMetadata,
    ) => Promise<Metadata>)
    component: React.ComponentType<{
    params: InferParams<Params>;
    searchParams?: InferParams<SearchParams>;
    data: InferLoaderData<Loader>;
    }>;
    params: InferParams<Params>
    searchParams?: InferParams<SearchParams>
    data: InferLoaderData<Loader>
    }>
    }

    async function parseParams<Schema extends readonly string[] | AnyZodObject>(
    params: Promise<Record<string, string>>,
    schema?: Schema
    _params: Promise<Record<string, string>>,
    schema?: Schema,
    ) {
    if (schema && "parse" in schema) {
    const props = await params;
    return schema.parse(props) as InferParams<Schema>;
    const params = await _params

    if (schema && 'parse' in schema) {
    return schema.parse(params) as InferParams<Schema>
    }

    return Promise.resolve(params) as InferParams<Schema>;
    return params as InferParams<Schema>
    }

    export const createPage = <
    const Params extends readonly string[] | AnyZodObject,
    const SearchParams extends readonly string[] | AnyZodObject,
    Loader extends LoaderFn<Params, SearchParams> = LoaderFn<Params, SearchParams>
    Loader extends LoaderFn<Params, SearchParams> = LoaderFn<
    Params,
    SearchParams
    >,
    >(
    props: CreatePageProps<Params, SearchParams, Loader>
    props: CreatePageProps<Params, SearchParams, Loader>,
    ) => {
    const {
    params: paramsSchema,
    searchParams: searchParamsSchema,
    component: PageComponent,
    loader,
    metadata,
    } = props;
    } = props

    // We don't really care about the types here since it's internal

    async function Page(props: any) {
    const params = await parseParams(props.params, paramsSchema);
    const params = await parseParams(props.params, paramsSchema)
    const searchParams = await parseParams(
    props.searchParams,
    searchParamsSchema
    );
    searchParamsSchema,
    )

    let pageProps: any = {
    params,
    searchParams,
    };
    }

    if (typeof loader === "function") {
    const data = await loader(pageProps);
    if (typeof loader === 'function') {
    const data = await loader(pageProps)

    pageProps = {
    ...pageProps,
    data,
    };
    }
    }

    return <PageComponent {...pageProps} />;
    return <PageComponent {...pageProps} />
    }

    if (typeof metadata === "function") {
    if (typeof metadata === 'function') {
    return {
    generateMetadata: async (
    {
    params,
    searchParams,
    }: {
    params: InferParams<Params>;
    searchParams: InferParams<SearchParams>;
    params: Promise<InferParams<Params>>
    searchParams: Promise<InferParams<SearchParams>>
    },
    parent: ResolvingMetadata
    parent: ResolvingMetadata,
    ) => {
    const _params = await parseParams(params, paramsSchema)
    const _searchParams = await parseParams(
    searchParams,
    searchParamsSchema,
    )

    const data =
    typeof loader === "function"
    typeof loader === 'function'
    ? await loader({
    params,
    searchParams,
    })
    : undefined;
    params: _params,
    searchParams: _searchParams,
    })
    : undefined

    return metadata(
    {
    params,
    searchParams,
    params: _params,
    searchParams: _searchParams,
    data,
    },
    parent
    );
    parent,
    )
    },
    Page,
    };
    }
    }

    return {
    metadata,
    Page,
    };
    };
    }
    }
  4. magicspon revised this gist Dec 17, 2024. 1 changed file with 112 additions and 113 deletions.
    225 changes: 112 additions & 113 deletions create-page.tsx
    Original file line number Diff line number Diff line change
    @@ -1,140 +1,139 @@
    import { AnyZodObject, z } from 'zod'
    import { Metadata, ResolvingMetadata } from 'next'
    /* eslint-disable @typescript-eslint/no-explicit-any */
    import { AnyZodObject, z } from "zod";
    import { Metadata, ResolvingMetadata } from "next";

    type InferParams<Params> = Params extends readonly string[]
    ? {
    [K in Params[number]]: string
    }
    : Params extends AnyZodObject
    ? z.infer<Params>
    : unknown
    ? {
    [K in Params[number]]: string;
    }
    : Params extends AnyZodObject
    ? z.infer<Params>
    : unknown;

    type LoaderFn<
    Params extends readonly string[] | AnyZodObject,
    SearchParams extends readonly string[] | AnyZodObject,
    Params extends readonly string[] | AnyZodObject,
    SearchParams extends readonly string[] | AnyZodObject
    > = (args: {
    params: InferParams<Params>
    searchParams: InferParams<SearchParams>
    }) => Promise<any>
    params: InferParams<Params>;
    searchParams: InferParams<SearchParams>;
    }) => Promise<any>;

    type InferLoaderData<Loader> = Loader extends (args: any) => Promise<infer T>
    ? T
    : unknown
    ? T
    : unknown;

    export interface CreatePageProps<
    Params extends readonly string[] | AnyZodObject,
    SearchParams extends readonly string[] | AnyZodObject,
    Loader extends LoaderFn<Params, SearchParams> = LoaderFn<
    Params,
    SearchParams
    >,
    Params extends readonly string[] | AnyZodObject,
    SearchParams extends readonly string[] | AnyZodObject,
    Loader extends LoaderFn<Params, SearchParams> = LoaderFn<Params, SearchParams>
    > {
    params?: Params
    searchParams?: SearchParams
    loader?: Loader
    metadata?:
    | Metadata
    | ((
    args: {
    params: InferParams<Params>
    searchParams: InferParams<SearchParams>
    data: InferLoaderData<Loader>
    },
    parent: ResolvingMetadata,
    ) => Promise<Metadata>)
    component: React.ComponentType<{
    params: InferParams<Params>
    searchParams?: InferParams<SearchParams>
    data: InferLoaderData<Loader>
    }>
    params?: Params;
    searchParams?: SearchParams;
    loader?: Loader;
    metadata?:
    | Metadata
    | ((
    args: {
    params: InferParams<Params>;
    searchParams: InferParams<SearchParams>;
    data: InferLoaderData<Loader>;
    },
    parent: ResolvingMetadata
    ) => Promise<Metadata>);
    component: React.ComponentType<{
    params: InferParams<Params>;
    searchParams?: InferParams<SearchParams>;
    data: InferLoaderData<Loader>;
    }>;
    }

    function parseParams<Schema extends readonly string[] | AnyZodObject>(
    params: Record<string, string>,
    schema?: Schema,
    async function parseParams<Schema extends readonly string[] | AnyZodObject>(
    params: Promise<Record<string, string>>,
    schema?: Schema
    ) {
    if (schema && 'parse' in schema) {
    return schema.parse(params) as InferParams<Schema>
    }
    if (schema && "parse" in schema) {
    const props = await params;
    return schema.parse(props) as InferParams<Schema>;
    }

    return params as InferParams<Schema>
    return Promise.resolve(params) as InferParams<Schema>;
    }

    export const createPage = <
    const Params extends readonly string[] | AnyZodObject,
    const SearchParams extends readonly string[] | AnyZodObject,
    Loader extends LoaderFn<Params, SearchParams> = LoaderFn<
    Params,
    SearchParams
    >,
    const Params extends readonly string[] | AnyZodObject,
    const SearchParams extends readonly string[] | AnyZodObject,
    Loader extends LoaderFn<Params, SearchParams> = LoaderFn<Params, SearchParams>
    >(
    props: CreatePageProps<Params, SearchParams, Loader>,
    props: CreatePageProps<Params, SearchParams, Loader>
    ) => {
    const {
    params: paramsSchema,
    searchParams: searchParamsSchema,
    component: PageComponent,
    loader,
    metadata,
    } = props
    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)
    // We don't really care about the types here since it's internal
    async function Page(props: any) {
    const params = await parseParams(props.params, paramsSchema);
    const searchParams = await parseParams(
    props.searchParams,
    searchParamsSchema
    );

    let pageProps: any = {
    params,
    searchParams,
    }
    let pageProps: any = {
    params,
    searchParams,
    };

    if (typeof loader === 'function') {
    const data = await loader(pageProps)
    if (typeof loader === "function") {
    const data = await loader(pageProps);

    pageProps = {
    ...pageProps,
    data,
    }
    }
    pageProps = {
    ...pageProps,
    data,
    };
    }

    return <PageComponent {...pageProps} />
    }
    return <PageComponent {...pageProps} />;
    }

    if (typeof metadata === 'function') {
    return {
    generateMetadata: async (
    {
    params,
    searchParams,
    }: {
    params: InferParams<Params>
    searchParams: InferParams<SearchParams>
    },
    parent: ResolvingMetadata,
    ) => {
    const data =
    typeof loader === 'function'
    ? await loader({
    params,
    searchParams,
    })
    : undefined
    if (typeof metadata === "function") {
    return {
    generateMetadata: async (
    {
    params,
    searchParams,
    }: {
    params: InferParams<Params>;
    searchParams: InferParams<SearchParams>;
    },
    parent: ResolvingMetadata
    ) => {
    const data =
    typeof loader === "function"
    ? await loader({
    params,
    searchParams,
    })
    : undefined;

    return metadata(
    {
    params,
    searchParams,
    data,
    },
    parent,
    )
    },
    Page,
    }
    }
    return metadata(
    {
    params,
    searchParams,
    data,
    },
    parent
    );
    },
    Page,
    };
    }

    return {
    metadata,
    Page,
    }
    }
    return {
    metadata,
    Page,
    };
    };
  5. magicspon revised this gist Aug 6, 2024. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion create-page.tsx
    Original file line number Diff line number Diff line change
    @@ -102,7 +102,7 @@ export const createPage = <

    if (typeof metadata === 'function') {
    return {
    generateMetaData: async (
    generateMetadata: async (
    {
    params,
    searchParams,
  6. @Pagebakers Pagebakers revised this gist Jul 1, 2024. 1 changed file with 27 additions and 9 deletions.
    36 changes: 27 additions & 9 deletions create-page.tsx
    Original file line number Diff line number Diff line change
    @@ -49,6 +49,17 @@ export interface CreatePageProps<
    }>
    }

    function parseParams<Schema extends readonly string[] | AnyZodObject>(
    params: Record<string, string>,
    schema?: Schema,
    ) {
    if (schema && 'parse' in schema) {
    return schema.parse(params) as InferParams<Schema>
    }

    return params as InferParams<Schema>
    }

    export const createPage = <
    const Params extends readonly string[] | AnyZodObject,
    const SearchParams extends readonly string[] | AnyZodObject,
    @@ -59,19 +70,26 @@ export const createPage = <
    >(
    props: CreatePageProps<Params, SearchParams, Loader>,
    ) => {
    const { component: PageComponent, title, loader, metadata } = props
    const {
    params: paramsSchema,
    searchParams: searchParamsSchema,
    component: PageComponent,
    loader,
    metadata,
    } = props

    // We don't really care about the types here since it's internally
    // We don't really care about the types here since it's internal
    async function Page(props: any) {
    let pageProps = {
    ...props,
    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({
    params: props.params,
    searchParams: props.searchParams,
    })
    const data = await loader(pageProps)

    pageProps = {
    ...pageProps,
    @@ -119,4 +137,4 @@ export const createPage = <
    metadata,
    Page,
    }
    }
    }
  7. @Pagebakers Pagebakers created this gist Jul 1, 2024.
    122 changes: 122 additions & 0 deletions create-page.tsx
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,122 @@
    import { AnyZodObject, z } from 'zod'
    import { Metadata, ResolvingMetadata } from 'next'

    type InferParams<Params> = Params extends readonly string[]
    ? {
    [K in Params[number]]: string
    }
    : Params extends AnyZodObject
    ? z.infer<Params>
    : unknown

    type LoaderFn<
    Params extends readonly string[] | AnyZodObject,
    SearchParams extends readonly string[] | AnyZodObject,
    > = (args: {
    params: InferParams<Params>
    searchParams: InferParams<SearchParams>
    }) => Promise<any>

    type InferLoaderData<Loader> = Loader extends (args: any) => Promise<infer T>
    ? T
    : unknown

    export interface CreatePageProps<
    Params extends readonly string[] | AnyZodObject,
    SearchParams extends readonly string[] | AnyZodObject,
    Loader extends LoaderFn<Params, SearchParams> = LoaderFn<
    Params,
    SearchParams
    >,
    > {
    params?: Params
    searchParams?: SearchParams
    loader?: Loader
    metadata?:
    | Metadata
    | ((
    args: {
    params: InferParams<Params>
    searchParams: InferParams<SearchParams>
    data: InferLoaderData<Loader>
    },
    parent: ResolvingMetadata,
    ) => Promise<Metadata>)
    component: React.ComponentType<{
    params: InferParams<Params>
    searchParams?: InferParams<SearchParams>
    data: InferLoaderData<Loader>
    }>
    }

    export const createPage = <
    const Params extends readonly string[] | AnyZodObject,
    const SearchParams extends readonly string[] | AnyZodObject,
    Loader extends LoaderFn<Params, SearchParams> = LoaderFn<
    Params,
    SearchParams
    >,
    >(
    props: CreatePageProps<Params, SearchParams, Loader>,
    ) => {
    const { component: PageComponent, title, loader, metadata } = props

    // We don't really care about the types here since it's internally
    async function Page(props: any) {
    let pageProps = {
    ...props,
    }

    if (typeof loader === 'function') {
    const data = await loader({
    params: props.params,
    searchParams: props.searchParams,
    })

    pageProps = {
    ...pageProps,
    data,
    }
    }

    return <PageComponent {...pageProps} />
    }

    if (typeof metadata === 'function') {
    return {
    generateMetaData: async (
    {
    params,
    searchParams,
    }: {
    params: InferParams<Params>
    searchParams: InferParams<SearchParams>
    },
    parent: ResolvingMetadata,
    ) => {
    const data =
    typeof loader === 'function'
    ? await loader({
    params,
    searchParams,
    })
    : undefined

    return metadata(
    {
    params,
    searchParams,
    data,
    },
    parent,
    )
    },
    Page,
    }
    }

    return {
    metadata,
    Page,
    }
    }