next.js: Infer Types leading to `props: never`

Describe the bug

export const getServerSideProps = async ({ params }) => {
  return { 
    props: { foo: "bar" } 
  }
};

export const Page = (props: InferGetServerSidePropsType<typeof getServerSideProps>) => { ... }

causes props: never

however the following works fine:

export const getServerSideProps = async ({ params }: GetServerSidePropsContext) => {
  return { 
    props: { foo: "bar" } 
  }
};

export const Page = (props: InferGetServerSidePropsType<typeof getServerSideProps>) => { ... }

as does:

export const getServerSideProps = async (ctx) => {
  const params = ctx.params;
  return { 
    props: { foo: "bar" } 
  }
};

export const Page = (props: InferGetServerSidePropsType<typeof getServerSideProps>) => { ... }

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 26
  • Comments: 28 (12 by maintainers)

Commits related to this issue

Most upvoted comments

It also happens in cases like this:

export const getServerSideProps = async (ctx) => {
  const { userId } = ctx.params
  const user = await getUser(userId)

  if(user) {
    return {
      props: { user }
    }
  } else {
    return { notFound: true }
  }
};

This also doesn’t work:

export const getServerSideProps: GetServerSideProps = async ({ params }) => {
  return { 
    props: { foo: "bar" } 
  }
};

export const Page = (props: InferGetServerSidePropsType<typeof getServerSideProps>) => { ... }

or

export const getServerSideProps: GetServerSideProps = async ({ params }: GetServerSidePropsContext) => {
  return { 
    props: { foo: "bar" } 
  }
};

export const Page = (props: InferGetServerSidePropsType<typeof getServerSideProps>) => { ... }

But removing the GetServerSideProps works:

export const getServerSideProps = async ({ params }: GetServerSidePropsContext) => {
  return { 
    props: { foo: "bar" } 
  }
};

export const Page = (props: InferGetServerSidePropsType<typeof getServerSideProps>) => { ... }

NextJS: v10.0.1

For me, the magic was to make sure the getServerSideProps function args are explicitly typed using the GetServerSidePropsContext type. Otherwise, the page props are inferred as any type, e.g. user: any.

I’ve found a workaround - first of all - something broke with 11.1.x as could use InferGetServerSidePropsType<typeof getServerSideProps> even with notFound using the below.

Here’s my hacky implementation that I just wrote:

utils/inferSSRProps.ts

/* eslint-disable @typescript-eslint/no-explicit-any */

type GetSSRResult<TProps> =
  //
  { props: TProps } | { redirect: any } | { notFound: true };

type GetSSRFn<TProps extends any> = (args: any) => Promise<GetSSRResult<TProps>>;

export type inferSSRProps<TFn extends GetSSRFn<any>> = TFn extends GetSSRFn<infer TProps>
  ? NonNullable<TProps>
  : never;

pages/somePage.tsx

import { inferSSRProps } from '../utils/inferSSRProps'
import { GetServerSidePropsContext } from "next";
import prisma from "@lib/prisma";

export default MyPage(props: inferSSRProps<typeof getServerSideProps>) {
  // ...
}

export const getServerSideProps = async (context: GetServerSidePropsContext) => {
  const post = await prisma.post.findFirst({
    where: {
      username: (context.query.slug as string).toLowerCase(),
    },
    select: {
      id: true,
      // ...
    },
  });
  if (!post) {
    return {
      notFound: true,
    } as const; // <-- important, this needs to be `as const`
  }
  return {
    props: {
     post,
    },
  };
}

I solved the issue by publishing my own infer type:

  • works with notFound
  • works with redirect
  • universal works for getStaticProps as well as getServerSideProps
  • no dependencies

Install

npm install infer-next-props-type --save-dev

Usage:

getStaticProps

import InferNextPropsType from 'infer-next-props-type'

export function getStaticProps() {
   return {
     props: { foo: 'bar' }
   }
}

export default function Page(props: InferNextPropsType<typeof getStaticProps>) {
  return ...
}

getServerSideProps

import InferNextPropsType from 'infer-next-props-type'

export function getServerSideProps() {
   return {
     props: { foo: 'bar' }
   }
}

export default function Page(props: InferNextPropsType<typeof getServerSideProps>) {
  return ...
}

https://www.npmjs.com/package/infer-next-props-type

Looks like this will hopefully be solved with Typescript 4.9!

https://twitter.com/leeerob/status/1563540593003106306

image

I found out something interessing. Type inference via InferGetServerSidePropsType<typeof getServerSideProps> seems to be working as long as I only return props.

return { 
  props: { 
    user: { 
      firstName,
      lastName 
    } 
  } 
};

If i additionally return a redirect or a notFound conditionally type inference stops working for me.

return {
  redirect: {
    destination: '',
    permanent: false,
  },
};
return {
  notFound: true
};

Setting types like this works:

GetServerSideProps<{
    user: User
    posts: Post[]
}>

This also works for me, but it would be more comfortable if it would work without the extra type specification. If the type is created like this, you would not need to infer it, because you can directly use it in the component.

I found out something interessing. Type inference via InferGetServerSidePropsType<typeof getServerSideProps> seems to be working as long as I only return props.

return { 
  props: { 
    user: { 
      firstName,
      lastName 
    } 
  } 
};

If i additionally return a redirect or a notFound conditionally type inference stops working for me.

return {
  redirect: {
    destination: '',
    permanent: false,
  },
};
return {
  notFound: true
};

Setting types like this works:

GetServerSideProps<{
    user: User
    posts: Post[]
}>

This also works for me, but it would be more comfortable if it would work without the extra type specification. If the type is created like this, you would not need to infer it, because you can directly use it in the component.

returning an empty props in redirect worked for me:

return {
  redirect: {
    destination: '',
    permanent: false,
  },
  props: {}
}

@HaNdTriX any reason for not updating the built-in type?

Hey @KATT, thanks for your solution!

You do not need to cast { notFound: true } to const if you change your GetSSRResult notFound type to boolean.

utils/inferSSRProps.ts

/* eslint-disable @typescript-eslint/no-explicit-any */

type GetSSRResult<TProps> =
  { props: TProps } | { redirect: any } | { notFound: boolean }; // <-------

type GetSSRFn<TProps extends any> = (args: any) => Promise<GetSSRResult<TProps>>;

export type inferSSRProps<TFn extends GetSSRFn<any>> = TFn extends GetSSRFn<infer TProps>
  ? NonNullable<TProps>
  : never;

I think you’re right, however this one works, where the function isn’t typed, and nor is the argument:

export const getServerSideProps = async (ctx) => {

I found a best work around. remove the : GetStaticProps from const getStaticProps = async () => {...}

Then the const SomePage: NextPage<InferGetStaticPropsType<typeof getStaticProps>>= ({/* inferred types */}){} can work correctly.

Problem was as said here https://github.com/vercel/next.js/issues/32434#issuecomment-993013691. So all after adding the : GetStaticProps , the return type of getStaticProps would be extended to { [key: string]: any; } by TS because it includes the original type of {‘foo’: string}.

Setting types like this works:

GetServerSideProps<{
    user: User
    posts: Post[]
}>

Because my type still has some edge cases to cover. Will deprecate the module as soon as we found the perfect working type and push the changes upstream.

This is a sneaky one, my string props were mistyped but that didn’t even cause a problem with strict TS. It only was an issue when I had an object in the props and wanted to access a property.

Here is my solution, key was to provide return type of getServerSideProps into GetServerSideProps type, here is example: https://gist.github.com/nenadfilipovic/f2dd9cb903da93a7d14ed1de6b3493b1