-
-
Save magicspon/c1647cf91909808be0a1eed5448bb56d to your computer and use it in GitHub Desktop.
Revisions
-
magicspon revised this gist
Feb 18, 2025 . 1 changed file with 14 additions and 9 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -25,7 +25,7 @@ type LoaderFn< }) => Promise<any> type CacheArgs = { 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 { draftMode: { isEnabled: false }, } const { isEnabled } = allowDraft ? await draftMode() : { isEnabled: false } const previewMode = { isEnabled } const props = { ...pageProps, draftMode: { isEnabled } } const { enabled = true, revalidate = DEFAULT_REVALIDATE_TIME, tags = undefined, keyParts = undefined, @@ -156,7 +160,7 @@ export const createPage = < const data = await fn() 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, draftMode } = await runLoader(pageProps) pageProps = { ...pageProps, data, draftMode, } } @@ -204,19 +208,20 @@ export const createPage = < searchParamsSchema, ) const { data, draftMode } = typeof loader === 'function' ? await runLoader({ params: _params, searchParams: _searchParams, }) : { draftMode: { isEnabled: false } } return metadata( { params: _params, searchParams: _searchParams, data, draftMode, }, parent, ) -
magicspon revised this gist
Feb 18, 2025 . 1 changed file with 89 additions and 9 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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, DraftMode> = LoaderFn< Params, 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, DraftMode> = LoaderFn< Params, SearchParams, DraftMode >, ShouldCache extends ShouldCacheFn< Params, SearchParams, DraftMode > = ShouldCacheFn<Params, SearchParams, DraftMode>, >( 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, previewMode } = await runLoader(pageProps) pageProps = { ...pageProps, data, draftMode: previewMode, } } @@ -124,13 +204,13 @@ export const createPage = < searchParamsSchema, ) const { data } = typeof loader === 'function' ? await runLoader({ params: _params, searchParams: _searchParams, }) : {} return metadata( { -
magicspon revised this gist
Feb 5, 2025 . 1 changed file with 72 additions and 59 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,139 +1,152 @@ // 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[] ? Record<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> }> } async function parseParams<Schema extends readonly string[] | AnyZodObject>( _params: Promise<Record<string, string>>, schema?: Schema, ) { const params = await _params 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, Loader extends LoaderFn<Params, SearchParams> = LoaderFn< Params, SearchParams >, >( props: CreatePageProps<Params, SearchParams, Loader>, ) => { 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 = await parseParams(props.params, paramsSchema) const searchParams = await parseParams( props.searchParams, searchParamsSchema, ) let pageProps: any = { params, searchParams, } if (typeof loader === 'function') { const data = await loader(pageProps) pageProps = { ...pageProps, data, } } return <PageComponent {...pageProps} /> } if (typeof metadata === 'function') { return { generateMetadata: async ( { params, searchParams, }: { params: Promise<InferParams<Params>> searchParams: Promise<InferParams<SearchParams>> }, parent: ResolvingMetadata, ) => { const _params = await parseParams(params, paramsSchema) const _searchParams = await parseParams( searchParams, searchParamsSchema, ) const data = typeof loader === 'function' ? await loader({ params: _params, searchParams: _searchParams, }) : undefined return metadata( { params: _params, searchParams: _searchParams, data, }, parent, ) }, Page, } } return { metadata, Page, } } -
magicspon revised this gist
Dec 17, 2024 . 1 changed file with 112 additions and 113 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,140 +1,139 @@ /* 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; 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>; }>; } async function parseParams<Schema extends readonly string[] | AnyZodObject>( params: Promise<Record<string, string>>, schema?: Schema ) { if (schema && "parse" in schema) { const props = await params; return schema.parse(props) 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> >( props: CreatePageProps<Params, SearchParams, Loader> ) => { 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 = await parseParams(props.params, paramsSchema); const searchParams = await parseParams( props.searchParams, searchParamsSchema ); let pageProps: any = { params, searchParams, }; if (typeof loader === "function") { const data = await loader(pageProps); 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, }; }; -
magicspon revised this gist
Aug 6, 2024 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -102,7 +102,7 @@ export const createPage = < if (typeof metadata === 'function') { return { generateMetadata: async ( { params, searchParams, -
Pagebakers revised this gist
Jul 1, 2024 . 1 changed file with 27 additions and 9 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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 { 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, @@ -119,4 +137,4 @@ export const createPage = < metadata, Page, } } -
Pagebakers created this gist
Jul 1, 2024 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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, } }