`@trpc/next` with `ssr: true` breaks `getServerSideProps`
Notes by @KATT:
- Solving this is blocked by https://github.com/vercel/next.js/discussions/28183.
- Keeping this open for visibility, but it likely won't be fixed.
- See warning-block at https://trpc.io/docs/ssr
@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.
Sounds like a bug.
On holidays for two weeks now, so if you want it fixed before that, submit a PR and I'll prob be able to review/merge/release.
I personally never use getserversideprops as it adds delay on route changes
Guidance
- Fork trpc
- Add failing test / showcase it doesn't work as expected in the next-starter-prisma example
- Submit fix + regression test
I'll add you as a contributor so you can do it easier
Can't reproduce. See 5b43446 and screenshots attached. Feel free to re-open with reproduction example.
The test has all sort of side-effecty things, not sure why (not good if you want to reproduce something), like this:
const postsQuery = trpc.useQuery(['posts.all']);
const addPost = trpc.useMutation('posts.add');
const utils = trpc.useContext();
But it does not matter, I decided not to use the library.
I used an existing example to quickly assess if what you reported was a bug or not since you didn't supply a reproduction example.
Curious - what did you end up using instead of trpc?
And what made you not use it?
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.
Thanks for the elaborate answer, @akomm! ❤️
I very much agree with your approach 🙏
Hi,
I have the same issue trying to add TRPC to an existing App... Any clue where it could come from ?
@etienne-dldc see if you can produce a reproduction using the next-prisma-starter example and open an issue!
I think this is related to this https://github.com/vercel/next.js/discussions/11183
_app.getInitialProps does not receive the data returned by [some-page] getServerSideProps. This is fine for the render of the page itself because Next inject the data from [some-page] getServerSideProps before the render. But it breaks when the ssrPrepass is done since props are not there yet...
Here is a codesandbox that show the issue: https://codesandbox.io/s/vibrant-galois-rjqkc?file=/pages/_app.js (reload the page and look at the console)
@etienne-dldc do you think there's anything that can be done inside of tRPC to counter it then?
Personally I never need to use getServerSideProps as I just expose whatever I want as a tRPC route that I use useQuery() on.
getServerSideProps slows down route changes, but getInitialProps doesn't.
Not sure, the problem come from ssrPrepass and I'm not sure to understand what it does.
In my case I was experimenting with Trpc but I rolled back because of this issue.
getServerSideProps slows down route changes, but getInitialProps doesn't.
I'm not sure about that, can you elaborate ?
@etienne-dldc even when you navigate client-side, the router still needs to execute the "getServerSideProps" on the server to get the props. Its done via a background fetch to the route, which only retrieves the result of getServerSideProps as json, instead of the whole rendered component. Then the props are populated on client-side. This trip to fetch the props and populate is what cause a delay on navigation and I guess what @KATT means.
However, getInitialProps implies using class components, which is quite an expectation. Unless I'v missed some other way around it, then please correct me.
@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.
@akomm You can use getInitialProps with function component: https://nextjs.org/docs/api-reference/data-fetching/getInitialProps
@akomm @KATT The server call to
getServerSidePropsdoes not slow down the page it only change the order in which event happen. Instead ofPageChange -> Load Datayou getLoad 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
getInitialPropswith 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
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.
@KATT regarding your previous answer, why does nextjs discourage to use getInitialProps and recommends using getServerSideProps or getStaticProps instead? https://nextjs.org/docs/api-reference/data-fetching/getInitialProps
Yes, for most applications this is true, but as a library author getInitialProps is super useful as it's the only thing that can do a prepass render the whole tree and do things like caching useQuery-calls, etc. And as I previously mentioned, the DX/UX you can get thanks to this is unparallel.
tRPC's plugin is largely based on urql's implementation - you can read more details about theirs here: https://formidable.com/open-source/urql/docs/advanced/server-side-rendering/
Solving this is blocked by https://github.com/vercel/next.js/discussions/28183.
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"}
-
getServerSidePropsgets called before the Home page is rendered. The page gets rendered with a fullpropsobject.
Actual:
- The page is rendered with empty props,
getServerSidePropsis 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"}
Setting ssr to false "solves" this issue.
I'm unsure what the drawbacks of this are but it seems like a workaround at the moment.
I guess the query hook will only run on client and not server then. When I last read the docs it was not explained, this is just what I think it does and would make sense to me. I sometimes wish docs for components that are isomorphic clearly states "this can run ssr/client", this not. You have to guess and test often times. Sometimes its almost obvious, but you are never really sure.
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.
Note: it's still possible to do manual server-side rendering or static props with ssr: false using the getSSGHelper - https://trpc.io/docs/ssg
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 :)
Worth watching:
- Next.js "Layouts" RFC: https://nextjs.org/blog/layouts-rfc#data-fetching
- This comment specifically: https://github.com/vercel/next.js/discussions/37136#discussioncomment-2807508
Thank you for the this thread, I had a hard time figuring out why the props are undefined :)
Could someone explain to me why ssr: false actually fixes this? I can see the server returning the html just as I would expect.
I mean It's counterintuitive since we want to enable ssr, right?
@apieceofbart there are two independent things that aren't exactly the same.
- Your components (JSX/TSX) can re rendered on server or client.
- The
getServerSidePropscan 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.
@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.