trpc: `@trpc/next` with `ssr: true` breaks `getServerSideProps`

Notes by @KATT:


@trpc/next: 8.0.0-alpha.6

Using:

export default pipe(
  App,
  withTRPC({
    ssr: true,
    config: (_ctx) => ({
      url: "http://localhost:3000/api/trpc",
      queryClientConfig: {
        defaultOptions: {
          queries: {
            staleTime: 600
          }
        }
      }
    }),
  })
)

Any page with getServerSideProps, for example:

export const getServerSideProps: GetServerSideProps<Props> = async () => {
  return {
    props: {
      user: {
        name: 'x',
        email: "y"
      }
    }
  }
}

Will receive undefined props, then get refreshed with props (the latter usually never executes due to errors using undefined)

Removing withTRPC hof stops this behavior.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 9
  • Comments: 33 (19 by maintainers)

Commits related to this issue

Most upvoted comments

Alex, I truly appreciate what you’ve done with this library and I understand that this is a limitation of Next.js.

I’m glad we have a workaround and I don’t mean to put pressure on you to deliver anything here. Thanks again for the solid lib.

For me, the main value of TRPC is a typesafe API between my frontend and my backend using shared types. The rest is just gravy on top 😃

@apieceofbart there are two independent things that aren’t exactly the same.

  1. Your components (JSX/TSX) can re rendered on server or client.
  2. The getServerSideProps can be used when rendering component on server or client.

IIRC, its a while back:

the option ssr in trpc only affects the rendering of JSX/TSX server-side. It uses getInitialProps. But in getInitialProps you don’t get access to the result ofgetServerSideProps - this is why its currently undefined with ssr: true. When you set ssr: false you do not disable getServersideProps, so everything that comes from it as a result can be rendered server-side, once the ssr feature is disabled, because then getInitialProps is not used.

I’m a one man band making tRPC, all example projects, and docs… there are limits to what I have bandwidth to do. Super keen to take on pull requests from contributors with the gaps as you learn the edge cases.

This issue of combining getSSR+getSSG and getInitialProps is out of my hands to fix as it’s an issue with Next.js - if you have ideas for workarounds, they are welcome as well.

If you encounter this, set ssr: false in your _app.tsx.

I just encountered this issue. I wanted to use getServerSideProps for authorization but the page still gets rendered before the props gets loaded (and then re-rendered after that).

Steps to reproduce: Create a sample project npx create-next-app --example https://github.com/trpc/trpc --example-path examples/next-prisma-starter trpc-prisma-starter

Create src/pages/home.tsx

export default function Home(props) {
  console.log('Rendering with props ' + JSON.stringify(props));
  return (
    <>
      <h1>Props v</h1>
      <p>{JSON.stringify(props)}</p>
    </>
  );
}

export async function getServerSideProps(context) {
  console.log('Getting server side props');
  return {
    props: {
      firstName: 'Hello',
    },
  };
}

Going to localhost:3000/home will log the following in the server’s console:

Rendering with props {}
Getting server side props
Rendering with props {"trpcState":{"json":{"mutations":[],"queries":[]}},"firstName":"Hello"}

Expected:

Getting server side props
Rendering with props {"trpcState":{"json":{"mutations":[],"queries":[]}},"firstName":"Hello"}
  • getServerSideProps gets called before the Home page is rendered. The page gets rendered with a full props object.

Actual:

  • The page is rendered with empty props, getServerSideProps is then called and the page is re-rendered with the props.
Rendering with props {}
Getting server side props
Rendering with props {"trpcState":{"json":{"mutations":[],"queries":[]}},"firstName":"Hello"}

This issue seems valid after all. Sorry for initially closing it. It’s for the same as urql can’t do it, see here - https://github.com/FormidableLabs/urql/discussions/1091.

I’m doing some exploratory stuff to see if there’s a workaround.

@akomm Thank you for your comment - I do not fully understand it but I’ll read some more about trpc. From what I understand it uses getInitialProps behind the scenes when ssr is set to true.

Default GH search includes all closed issues, so this issue will still pop up when searching for getServerSideProps or ssr (more keywords, yay) in case someone wants to read on this issue. If more people will report this instead of searching, we can reopen/pin it.

@KATT Should this issue be closed if its a wontfix?

@akomm @KATT The server call to getServerSideProps does not slow down the page it only change the order in which event happen. Instead of PageChange -> Load Data you get Load Data -> PageChange.

  • getServerSideProps: user clicks -> routeChangeStart --> ⏳ […delay] --> routeChangeComplete
  • getInitialProps: user clicks -> routeChangeStart --> 🚀 instant --> routeChangeComplete

The difference is that you’ll have to handle the loading client-side with getInitialProps.

For an implementation of this with tRPC doesn’t require a loading you can check out https://typescript.careers which optimistically loads the data of all routes on mount (as 1 single batch request), making navigation feel instant.

If you use getStaticProps you can get instant loading as well.

See https://nextjs.org/docs/api-reference/next/router#routerevents

@akomm You can use getInitialProps with function component: https://nextjs.org/docs/api-reference/data-fetching/getInitialProps

That’s what we do in tRPC - see https://github.com/trpc/trpc/blob/main/packages/next/src/withTRPC.tsx#L85

Thanks for the elaborate answer, @akomm! ❤️

I very much agree with your approach 🙏

I evaluate the benefit of a library vs. cost of patching and rewiring things to own needs (plus the hidden cost to keep adjusting those adaptations with updates). Based on that I either live with things not perfectly fit and adjust them, or I do it myself, or I use alternative (in this case I just do it myself).

In general, I’d rather write some code myself than adding a library. If the cost is not high. I have lot of projects to maintain and I want it to be as simple as possible. Every thing that does not fit or has to be “adapted” artificially to match, and the adaptation looks like ugly code, is a problem I try to avoid. On top of it, you are hooked to get things fixed to release cycles or hand-wiring fix-commits in dependencies…

I figured out for my need of typed API and elimination of redundancy, it does not take much (using fp-ts/io-ts). And I don’t need to create “type adapters” etc, for things to work nicely. Also: libraries will always be opinionated in some way.

As about the problem: I’d probably find the exact situation in which the problem occurs, but there are so many bugs all over the place (in other libraries), that I just don’t have the time messing with all them. At times I’d jump from one bug in a package to another bug in the same package. And sometimes I can’t just replace it with own code (cost to high).

The other way around examples: nextjs or pdf parser is something - to name an example - I would rather take the adjusting, than the cost of doing AND maintaining it myself.