Skip to content

Instantly share code, notes, and snippets.

@lukasbuenger
Created April 22, 2021 17:46
Show Gist options
  • Save lukasbuenger/c0df2bcb90381aeb33174e903a62d44e to your computer and use it in GitHub Desktop.
Save lukasbuenger/c0df2bcb90381aeb33174e903a62d44e to your computer and use it in GitHub Desktop.
export { mergeServerSideProps } from './merge-server-side-props'
export { mergeStaticProps } from './merge-static-props'
import { GetServerSideProps } from 'next'
export function mergeServerSideProps<TPropsA, TPropsB>(
a: GetServerSideProps<TPropsA>,
b: GetServerSideProps<TPropsB>,
): GetServerSideProps<TPropsA & TPropsB>
export function mergeServerSideProps<
TPropsA,
TPropsB,
TPropsC
>(
a: GetServerSideProps<TPropsA>,
b: GetServerSideProps<TPropsB>,
c: GetServerSideProps<TPropsC>,
): GetServerSideProps<TPropsA & TPropsB & TPropsC>
export function mergeServerSideProps<
TPropsA,
TPropsB,
TPropsC,
TPropsD
>(
a: GetServerSideProps<TPropsA>,
b: GetServerSideProps<TPropsB>,
c: GetServerSideProps<TPropsC>,
d: GetServerSideProps<TPropsD>,
): GetServerSideProps<TPropsA & TPropsB & TPropsC & TPropsD>
export function mergeServerSideProps<
TPropsA,
TPropsB,
TPropsC,
TPropsD,
TPropsE
>(
a: GetServerSideProps<TPropsA>,
b: GetServerSideProps<TPropsB>,
c: GetServerSideProps<TPropsC>,
d: GetServerSideProps<TPropsD>,
e: GetServerSideProps<TPropsE>,
): GetServerSideProps<
TPropsA & TPropsB & TPropsC & TPropsD & TPropsE
>
export function mergeServerSideProps<
TPropsA,
TPropsB,
TPropsC,
TPropsD,
TPropsE,
TPropsF
>(
a: GetServerSideProps<TPropsA>,
b: GetServerSideProps<TPropsB>,
c: GetServerSideProps<TPropsC>,
d: GetServerSideProps<TPropsD>,
e: GetServerSideProps<TPropsE>,
f: GetServerSideProps<TPropsF>,
): GetServerSideProps<
TPropsA & TPropsB & TPropsC & TPropsD & TPropsE & TPropsF
>
export function mergeServerSideProps<
TPropsA,
TPropsB,
TPropsC,
TPropsD,
TPropsE,
TPropsF
>(
a: GetServerSideProps<TPropsA>,
b: GetServerSideProps<TPropsB>,
c: GetServerSideProps<TPropsC>,
d: GetServerSideProps<TPropsD>,
e: GetServerSideProps<TPropsE>,
f: GetServerSideProps<TPropsF>,
...fns: GetServerSideProps[]
): GetServerSideProps<
TPropsA &
TPropsB &
TPropsC &
TPropsD &
TPropsE &
TPropsF &
Record<string, unknown>
>
export function mergeServerSideProps(
...fns: GetServerSideProps[]
): GetServerSideProps {
return async (ctx) => {
const results = await Promise.all(
fns.map((fn) => fn(ctx)),
)
return Object.assign({}, ...results)
}
}
import { GetStaticProps } from 'next'
export function mergeStaticProps<TPropsA, TPropsB>(
a: GetStaticProps<TPropsA>,
b: GetStaticProps<TPropsB>,
): GetStaticProps<TPropsA & TPropsB>
export function mergeStaticProps<TPropsA, TPropsB, TPropsC>(
a: GetStaticProps<TPropsA>,
b: GetStaticProps<TPropsB>,
c: GetStaticProps<TPropsC>,
): GetStaticProps<TPropsA & TPropsB & TPropsC>
export function mergeStaticProps<
TPropsA,
TPropsB,
TPropsC,
TPropsD
>(
a: GetStaticProps<TPropsA>,
b: GetStaticProps<TPropsB>,
c: GetStaticProps<TPropsC>,
d: GetStaticProps<TPropsD>,
): GetStaticProps<TPropsA & TPropsB & TPropsC & TPropsD>
export function mergeStaticProps<
TPropsA,
TPropsB,
TPropsC,
TPropsD,
TPropsE
>(
a: GetStaticProps<TPropsA>,
b: GetStaticProps<TPropsB>,
c: GetStaticProps<TPropsC>,
d: GetStaticProps<TPropsD>,
e: GetStaticProps<TPropsE>,
): GetStaticProps<
TPropsA & TPropsB & TPropsC & TPropsD & TPropsE
>
export function mergeStaticProps<
TPropsA,
TPropsB,
TPropsC,
TPropsD,
TPropsE,
TPropsF
>(
a: GetStaticProps<TPropsA>,
b: GetStaticProps<TPropsB>,
c: GetStaticProps<TPropsC>,
d: GetStaticProps<TPropsD>,
e: GetStaticProps<TPropsE>,
f: GetStaticProps<TPropsF>,
): GetStaticProps<
TPropsA & TPropsB & TPropsC & TPropsD & TPropsE & TPropsF
>
export function mergeStaticProps<
TPropsA,
TPropsB,
TPropsC,
TPropsD,
TPropsE,
TPropsF
>(
a: GetStaticProps<TPropsA>,
b: GetStaticProps<TPropsB>,
c: GetStaticProps<TPropsC>,
d: GetStaticProps<TPropsD>,
e: GetStaticProps<TPropsE>,
f: GetStaticProps<TPropsF>,
...fns: GetStaticProps[]
): GetStaticProps<
TPropsA &
TPropsB &
TPropsC &
TPropsD &
TPropsE &
TPropsF &
Record<string, unknown>
>
export function mergeStaticProps(
...fns: GetStaticProps[]
): GetStaticProps {
return async (ctx) => {
const results = await Promise.all(
fns.map((fn) => fn(ctx)),
)
return Object.assign({}, ...results)
}
}
@lukasbuenger
Copy link
Author

lukasbuenger commented Apr 22, 2021

This is intended to serve as as starting point for Typescript people who want to compose Next.js' page property aggregator functions in a type-safe manner.

What does it do?

Somewhere in your app:

import { GetServerSideProps } from 'next'
const getStuff: GetServerSideProps = async (ctx) => ({
   props: {
      stuff: await fetchWithCtx('from/api-endpoint`, ctx)
   }
)}

At some other code path in your app

import { GetServerSideProps } from 'next'
const getOtherStuff: GetServerSideProps = async (ctx) => ({
   props: {
      otherStuff: await fetchWithCtx('from/another-api-endpoint`, ctx)
   }
)}

On a page

import { GetServerSideProps, InferGetServerSidePropsType } from 'next'
import { getStuff } from './somewhere'
import { getOtherStuff } from './somewhere-else'

export const getServerSideProps = mergeServerSideProps(getStuff, getOtherStuff)

type PageProps = InferGetServerSidePropsType<typeof getServerSideProps>

export default Page(props: PageProps) {
    // Handle type-safe props
}

Usage

Copy-Paste.

Inline with what Next.js examples are usually suggesting in terms of folder structure, this could live at e.g.:

  • lib/merge-page-props/
    • merge-static-props.ts
    • merge-server-side-props.ts
    • merge-page-props.ts (rename this to index.ts to give the whole thing a library feel)

As this is really only a glorified and type-cast variant of a regular parallelised async call (as a matter of fact that's what Promise.all really is), you probably want to make sure that none of the composed functions are depending on execution order. Consider a page that is restricted to authenticated users only:

import { GetServerSideProps, InferGetServerSidePropsType } from 'next'
import { getStuff } from './somewhere'
import { getOtherStuff } from './somewhere-else'
import { getSession } from './auth'

const getRestrictedContents = mergeServerSideProps(
    getStuff,
    getOtherStuff,
)

export const getServerSideProps: GetServerSideProps = async (ctx) => {
    const session = await getSession(ctx)
    if (!session) {
        return { props: null }
        // Or redirect or whatever
    }
    return await getRestrictedContents(ctx)
}

type RestrictedPageProps = InferGetServerSidePropsType<
  typeof getServerSideProps
>

export default function RestrictedPage(props: RestrictedPageProps) {
    // and so on
}

Why is this not an NPM package?

Have you looked at the code? The actual JS instructions are effing trivial. It's all about not being too lazy to explicitly define overloads for a very simple async object merger. I don't think that this kind of stuff is worth yet another hard dependency on yet another package. Hence this being a Gist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment