Khalil
Khalil8mo ago

Convex+Clerk: sign out unhandled runtime error

Hi Convex team, I've been battling this issue for a couple of weeks and have no idea what to try anymore. I have the latest versions of everything: Clerk, Convex, Nextjs For Convex, I combine RSC and client components, using fetchQuery for RSC and of course useQuery and useMutation on the client. I setup everything according to both, Clerk and Convex documentation in the middleware.ts I have:
// middleware.ts

import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";

const isProtectedRoute = createRouteMatcher(["/projects(.*)", "/waitlist(.*)"]);

export default clerkMiddleware((auth, req) => {
const signInUrl = new URL("/sign-in", req.url).toString();
if (isProtectedRoute(req)) {
auth().protect({ unauthenticatedUrl: signInUrl });
}

return NextResponse.next();
});

export const config = {
matcher: [
// Exclude files with a "." followed by an extension, which are typically static files.
// Exclude files in the _next directory, which are Next.js internals.
"/((?!.+\\.[\\w]+$|_next|monitoring).*)",
// Re-include any files in the api or trpc folders that might have an extension
"/(api|trpc)(.*)",
],
};
// middleware.ts

import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";

const isProtectedRoute = createRouteMatcher(["/projects(.*)", "/waitlist(.*)"]);

export default clerkMiddleware((auth, req) => {
const signInUrl = new URL("/sign-in", req.url).toString();
if (isProtectedRoute(req)) {
auth().protect({ unauthenticatedUrl: signInUrl });
}

return NextResponse.next();
});

export const config = {
matcher: [
// Exclude files with a "." followed by an extension, which are typically static files.
// Exclude files in the _next directory, which are Next.js internals.
"/((?!.+\\.[\\w]+$|_next|monitoring).*)",
// Re-include any files in the api or trpc folders that might have an extension
"/(api|trpc)(.*)",
],
};
in my authed layout I have:
<ClerkLoaded>
<Authenticated>{children}</Authenticated>
<Unauthenticated>
<RedirectToSignIn />
</Unauthenticated>
</ClerkLoaded>
<ClerkLoading>
<Loading />
</ClerkLoading>
<ClerkLoaded>
<Authenticated>{children}</Authenticated>
<Unauthenticated>
<RedirectToSignIn />
</Unauthenticated>
</ClerkLoaded>
<ClerkLoading>
<Loading />
</ClerkLoading>
And I return null for all queries, when ctx.auth.getUserIdentity() is null. The problem happens when I logout, the app just crashes with an "unhandled runtime error"
// browser console logs
clerk.browser.js:2 Uncaught (in promise) Error
at o._fetch (clerk.browser.js:2:80700)
at async ne.create (clerk.browser.js:2:118896)
// browser console logs
clerk.browser.js:2 Uncaught (in promise) Error
at o._fetch (clerk.browser.js:2:80700)
at async ne.create (clerk.browser.js:2:118896)
This is also happening on production
22 Replies
Michal Srb
Michal Srb8mo ago
Thanks @Khalil can you: 1. Include the full error stack trace (it looks minimized maybe, can you get a better one from dev maybe?) 2. Can you strip your app down and share it on GitHub? (if it's easy and feasible) 3. Check the Network tab, are there any failed requests when you click sign out?
Khalil
KhalilOP8mo ago
there is an unauthorized request (401) in the network, confirming it's client-side
Khalil
KhalilOP8mo ago
No description
Khalil
KhalilOP8mo ago
Can you strip your app down and share it on GitHub? (if it's easy and feasible)
I could if necessary, but will probably have it in a few hours or later this week
Michal Srb
Michal Srb8mo ago
So definitely what's happening is that the token is being fetched even though the client is already logged out What does your ConvexProviderWithClerk callsite looks like? Standard? (paste the whole file including imports please)
Khalil
KhalilOP8mo ago
"use client";

import { ClerkProvider, useAuth } from "@clerk/nextjs";
import { dark as darkThemeClerk } from "@clerk/themes";
import { ConvexReactClient } from "convex/react";
import { ConvexProviderWithClerk } from "convex/react-clerk";
import { AppProgressBar as ProgressBar } from "next-nprogress-bar";
import { ThemeProvider as NextThemesProvider, useTheme } from "next-themes";
import { type ThemeProviderProps } from "next-themes/dist/types";
import * as React from "react";

import { TooltipProvider } from "@fastmind/ui";

import { env } from "~/env.mjs";

const ThemeProvider = ({ children, ...props }: ThemeProviderProps) => {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
};

const ServicesProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
const convex = new ConvexReactClient(env.NEXT_PUBLIC_CONVEX_URL);
const { resolvedTheme } = useTheme();
return (
<ClerkProvider
signInFallbackRedirectUrl="/sign-in"
signUpFallbackRedirectUrl="/sign-up"
afterSignOutUrl="/sign-in"
appearance={{
baseTheme: resolvedTheme === "dark" ? darkThemeClerk : undefined,
variables: {
colorPrimary: "hsl(263.4, 70%, 50.4%)",
},
}}
>
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
{children}
</ConvexProviderWithClerk>
</ClerkProvider>
);
};

export const Providers: React.FC<React.PropsWithChildren> = ({ children }) => {
return (
<ThemeProvider attribute="class" defaultTheme="dark">
<ServicesProvider>
<ProgressBar
options={{ showSpinner: false }}
color="#39c4a4"
shallowRouting
/>
<TooltipProvider>{children}</TooltipProvider>
</ServicesProvider>
</ThemeProvider>
);
};
"use client";

import { ClerkProvider, useAuth } from "@clerk/nextjs";
import { dark as darkThemeClerk } from "@clerk/themes";
import { ConvexReactClient } from "convex/react";
import { ConvexProviderWithClerk } from "convex/react-clerk";
import { AppProgressBar as ProgressBar } from "next-nprogress-bar";
import { ThemeProvider as NextThemesProvider, useTheme } from "next-themes";
import { type ThemeProviderProps } from "next-themes/dist/types";
import * as React from "react";

import { TooltipProvider } from "@fastmind/ui";

import { env } from "~/env.mjs";

const ThemeProvider = ({ children, ...props }: ThemeProviderProps) => {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
};

const ServicesProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
const convex = new ConvexReactClient(env.NEXT_PUBLIC_CONVEX_URL);
const { resolvedTheme } = useTheme();
return (
<ClerkProvider
signInFallbackRedirectUrl="/sign-in"
signUpFallbackRedirectUrl="/sign-up"
afterSignOutUrl="/sign-in"
appearance={{
baseTheme: resolvedTheme === "dark" ? darkThemeClerk : undefined,
variables: {
colorPrimary: "hsl(263.4, 70%, 50.4%)",
},
}}
>
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
{children}
</ConvexProviderWithClerk>
</ClerkProvider>
);
};

export const Providers: React.FC<React.PropsWithChildren> = ({ children }) => {
return (
<ThemeProvider attribute="class" defaultTheme="dark">
<ServicesProvider>
<ProgressBar
options={{ showSpinner: false }}
color="#39c4a4"
shallowRouting
/>
<TooltipProvider>{children}</TooltipProvider>
</ServicesProvider>
</ThemeProvider>
);
};
Michal Srb
Michal Srb8mo ago
That looks fine. Here's what I don't understand: The stack trace suggests that the error is thrown from ConvexProviderWithClerk, where we call getToken. But the call to getToken is wrapped in a try catch. So when Clerk throws there it should not be an uncaught error.
Khalil
KhalilOP8mo ago
btw it's reproducible right now on prod if it helps
Khalil
KhalilOP8mo ago
fastmind
Fastmind is a platform for building and deploying AI chatbots.
Khalil
KhalilOP8mo ago
signup and logout not sure if that is helpful, I also enable convex debug logs, but could not find anything wrong there
Michal Srb
Michal Srb8mo ago
My guess is that the error is not the problem. Instead the issue is with navigating to sign-in after the sign out
Khalil
KhalilOP8mo ago
I did notice that on sign out, the current page is refetched, for whatever reason
Michal Srb
Michal Srb8mo ago
You have <RedirectToSignIn /> in your code snippet, that's what redirects there The question is why doesn't the sign in page render. But that seems to be some RSC issue
Michal Srb
Michal Srb8mo ago
Some layout is rendering, but then no page
No description
Khalil
KhalilOP8mo ago
that is after signin or signout? that <div> matches the /sign-in clerk page
// app/sign-in/[[...sign-in]]/page.tsx
import { SignIn } from "@clerk/nextjs";

export default function Page() {
return (
<div className="flex h-screen w-full items-center justify-center">
<SignIn
path="/sign-in"
signUpUrl="/sign-up"
fallbackRedirectUrl="/projects"
/>
</div>
);
}
// app/sign-in/[[...sign-in]]/page.tsx
import { SignIn } from "@clerk/nextjs";

export default function Page() {
return (
<div className="flex h-screen w-full items-center justify-center">
<SignIn
path="/sign-in"
signUpUrl="/sign-up"
fallbackRedirectUrl="/projects"
/>
</div>
);
}
Michal Srb
Michal Srb8mo ago
Yeah, so after sign out, there is a redirect to the signin page, which seems to be server rendered and only returned via the RSC payload. But somehow the page doesn't show up. The error is definitely not uncaught, because I tried pausing on uncaught exceptions in the source panel, but did not get paused on sign out Try to redirect to sign on a button click somewhere while still signed in/out, see if it works correctly. It could be a bug in Next.js or Clerk
Khalil
KhalilOP8mo ago
is there an example repo using convex + clerk core 2 that I can refer to?
Michal Srb
Michal Srb8mo ago
GitHub
GitHub - get-convex/turbo-expo-nextjs-clerk-convex-monorepo at srb/...
Monorepo template with Turborepo, Next.js, Expo, Clerk, Convex - GitHub - get-convex/turbo-expo-nextjs-clerk-convex-monorepo at srb/upgrade-to-latest
Michal Srb
Michal Srb8mo ago
Also what happens when you remove ConvexProviderWithClerk (and any code that depends on it). Then you can sign in and sign out to see if it's actually Convex related (I expect not).
Khalil
KhalilOP8mo ago
managed to fix it! now there is another issue, but it seems Clerk related the problem was this:
const ServicesProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
const convex = new ConvexReactClient(env.NEXT_PUBLIC_CONVEX_URL);
const { resolvedTheme } = useTheme();
return (
<ClerkProvider
signInFallbackRedirectUrl="/sign-in"
signUpFallbackRedirectUrl="/sign-up"
afterSignOutUrl="/sign-in"
appearance={{
baseTheme: resolvedTheme === "dark" ? darkThemeClerk : undefined,
variables: {
colorPrimary: "hsl(263.4, 70%, 50.4%)",
},
}}
>
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
{children}
</ConvexProviderWithClerk>
</ClerkProvider>
);
};
const ServicesProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
const convex = new ConvexReactClient(env.NEXT_PUBLIC_CONVEX_URL);
const { resolvedTheme } = useTheme();
return (
<ClerkProvider
signInFallbackRedirectUrl="/sign-in"
signUpFallbackRedirectUrl="/sign-up"
afterSignOutUrl="/sign-in"
appearance={{
baseTheme: resolvedTheme === "dark" ? darkThemeClerk : undefined,
variables: {
colorPrimary: "hsl(263.4, 70%, 50.4%)",
},
}}
>
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
{children}
</ConvexProviderWithClerk>
</ClerkProvider>
);
};
The const convex = new ConvexReactClient(env.NEXT_PUBLIC_CONVEX_URL); declaration should be outside of a React component for whatever reason
Michal Srb
Michal Srb8mo ago
Ah nice! That makes sense, you get a new Convex client every time the component renders otherwise. Thanks for sharing the fix! (I should have spotted that earlier, my bad!)

Did you find this page helpful?