warmbowski
warmbowski3mo ago

SSR not working using Tanstack-Start, Convex with Tanstack-Query, Clerk auth.

There seems to be a big problem when SSR kicks in for routes that have convex function calls that require authentication. What I see is the __root beforeLoad calls the server function that returns the token and then calls serverHTTPClient.setAuth(token), so the convexClient has the token when the first convex functions are called, but the user isn't set yet on the convex backend. So the functions throw when they find no auth user (I see the "Not authenticated" errors in the convex dev server console) and that errors out the SSR with a message like the one below showing up in the web server console and the browser when the page loads. So there is a big delay between when the token is retrieved and processed in the serverFunction and when the convex server actually sets the user for auth.getUserIdentity(). I feel like the convex auth with clerk is a black box to me, and I also feel like the authentication on the convex server needs to be triggered in that before load somehow. All routes work fine in the client when SSR is disabled. Has anyone else run into this? Any suggestions would be much appreciated for troubleshooting. For now, I'm just going without SSR.
Uncaught Error: Switched to client rendering because the server rendering errored:

[Request ID: 3cb284c91a255ec5] Server Error
Uncaught Error: User not authenticated
at handler (../../convex/functions/members.ts:172:22)

at handler (../../convex/functions/members.ts:172:22)

at ConvexHttpClient.action (file:///..../iounia/node_modules/.pnpm/convex@1.24.3_@cl…0__react@19.1.0/node_modules/convex/dist/esm/browser/http_client.js:344:15)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async file:///..../iounia/node_modules/.pnpm/@convex-dev+react…oqamnnqqfk2a/node_modules/@convex-dev/react-query/dist/esm/index.js:228:34
<OutletImpl>
component @ __root.tsx:80
<StartClient>
(anonymous) @ client.tsx:8
Uncaught Error: Switched to client rendering because the server rendering errored:

[Request ID: 3cb284c91a255ec5] Server Error
Uncaught Error: User not authenticated
at handler (../../convex/functions/members.ts:172:22)

at handler (../../convex/functions/members.ts:172:22)

at ConvexHttpClient.action (file:///..../iounia/node_modules/.pnpm/convex@1.24.3_@cl…0__react@19.1.0/node_modules/convex/dist/esm/browser/http_client.js:344:15)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async file:///..../iounia/node_modules/.pnpm/@convex-dev+react…oqamnnqqfk2a/node_modules/@convex-dev/react-query/dist/esm/index.js:228:34
<OutletImpl>
component @ __root.tsx:80
<StartClient>
(anonymous) @ client.tsx:8
8 Replies
warmbowski
warmbowskiOP3mo ago
I'm curious if it's possible to do the auth check in the convex backend either via function or httpAction, and if that auth check could make sure that convex backend has the usres auth set up before responding.
warmbowski
warmbowskiOP3mo ago
Okay, I fixed this nagging and untraceable error by sheer luck. I made some changes to my Tanstack router setup and it seemed to have fixed SSR so that it doesn't throw the error anymore, even thought I still see the unauthenticated errors in the convex dev server. I made a few changes around that time, but I think the key was in the attached image where I removed the the defaultPreloadStaleTime: 0, from my createRouter args. It seems to be running smoothly in SSR now. I will say, the clerk/convex integration during SSR could be improved to make sure that the convex backend has user authentication before any function calls. So I guess that would be a request for future improvements.
No description
warmbowski
warmbowskiOP3mo ago
I take it back, I still see this. I'm at a loss as to how to fix it. 😦 It just seems like there is a big delay between when Clerk is authenticated in the Provider and when the convexClient authenticate the user in the websocket. Is there any way to imperatively initiate the convexClient authentication and wait for it to return? I want to run it in a beforeLoad in tanstack-router and wait in it to block. If it's not done there, the router loaders will run and create these error messages and break SSR. I do a lot of queryClient.prefetchQuery in the router loaders so as to have the data cached for when the components run useSuspenseQuery. If not, would it be possible to add it to the convexClient?
warmbowski
warmbowskiOP3mo ago
Did some perf testing. It seems Clerk authenticates pretty quick after the component renders, at which point the Convex auth is kicked off. But depending on the complexity of the page, it can take anywhere from about 750ms to 1000+ms for the convex auth to complete. In the mean time, convex queries and actions are kicking off and throwing Not Authenticated errors.
No description
erquhart
erquhart3mo ago
The Convex client can't do anything until the Clerk Provider authenticates and produces a token, at which point, more or less immediately, the token gets passed through the Convex client to the Convex backend via websockets, which you can see in the messages. Shouldn't take as long as you're seeing, but for the unauthenticated errors beforehand, either don't load components that require auth until you're authenticated or use the skip arg in useQuery to keep authenticated queries from running prematurely: https://docs.convex.dev/client/react#skipping-queries
warmbowski
warmbowskiOP3mo ago
In the case of tanstack-start, it's not in the components, it's the loaders in the router, which all fire off simultaneously across all matched route files when the route is matched. The only way to block those is via beforeLoad, which convex doesn't provide a way to do that. I can skip the loaders with some conditionals, but this all would defeat the purpose of using tanstack-start and streaming SSR. I wonder if there needs to be some kind of tanstack-start custom middleware to help with this. This would most likely also affect Convex auth as well, but I am usure, as I haven't used it. As far as the lenght of time it's taking to authenticate convex, I assume that that time includes spinning up the websocket. I'm not sure, but maybe if there is a way to spin up the websocket sooner, then the auth would work better. Also, it could just be slow in dev. One of these days I will deploy and see if it works more smoothly. I just noticed that there is a tanstack channel, maybe I will check in there
erquhart
erquhart3mo ago
Sorry, completely missed the server side part here, yeah it's different. Tanstack channel is a good place to ask how folks are handling this.
warmbowski
warmbowskiOP3mo ago
I am realizing that during SSR on the server, the ConvexHttpClient (http request) is used get the data, not the ConvexClient (websocket). So it gets even more complicated. I set the auth token in serverHttpClient very early, before I even create the router instance. So not sure why I still get "not authenticated". OKAY! I think I got a lot of my woes figured out. There were a couple things going on due to the @convex-dev/react-query package:. Because I am using @tanstack/react-query and wrapping my convex functions, I am using this package. This package has lots of benefits, but a couple of problems during SSR.: - it calls queries with ConvexHttpClient.consistentQuery, which is experimental and give intermittent errors during SSR. I replaced with ConvexHttpClient.query in the package code and have no SSR problems. - if you wrap a convexAction in useSuspenseQuery for SSR, it creates a new instance of ConvexHttpClient which wipes out any auth settings you might have set. I replace the new instance with reusing the existing instance this.serverHttpClient and now have no auth problems during SSR. SSR is working prettty good now in Tanstack-start with these changes.

Did you find this page helpful?