Duplicate billable queries after refresh
EDIT: Start reading from this message: https://discord.com/channels/1019350475847499849/1424126402101510254/1424371756256460810
TLDR: When using Convex + Clerk in a React app, all the Convex queries in a page are executed two times when user refreshes the page. For example, if a page has 3 distinct useQuery functions and user refreshes, logs show 6 executed queries instead of 3. Even for a medium size app this can result in hundreds of thousands of additional (unnecessary) billed function calls per month.
54 Replies
Thanks for posting in <#1088161997662724167>.
Reminder: If you have a Convex Pro account, use the Convex Dashboard to file support tickets.
- Provide context: What are you trying to achieve, what is the end-user interaction, what are you seeing? (full error message, command output, etc.)
- Use search.convex.dev to search Docs, Stack, and Discord all at once.
- Additionally, you can post your questions in the Convex Community's <#1228095053885476985> channel to receive a response from AI.
- Avoid tagging staff unless specifically instructed.
Thank you!
@Napoleon its probably because you're mounting the queries before clerk is ready so it run once unauthenticated and once when it authenticates so convex will remount the query.
<ClerkProvider publishableKey={import.meta.env.VITE_CLERK_PUBLISHABLE_KEY!}>
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
<ConvexQueryCacheProvider>
<div style={{ padding: 32 }}>
<QueryConsumerA />
<QueryConsumerB />
</div>
</ConvexQueryCacheProvider>
</ConvexProviderWithClerk>
</ClerkProvider>
in this piece you can wrap the ConvexQueryCacheProvider with some hook that clerk provides that'll tell the provider that its authenticated something like <AuthValid> or <SignedIn> i'm not sure what clerk provides.
or you could just use some statement to check if authenticated before you mount the query /when passing args to query you can use "skip"
let me know if that makes sense
thank you, that's basically the only thing I haven't tried because I thought this couldn't be the issue. I will give it a try right now and let you know
No luck with this, still duplicated queries...
what did you try?
i'm not sure if this coming into play here, but a result that changes every millisecond isn't ideal to test query stability with
I tried adding additional auth checks. But it should work as is, I followed https://docs.convex.dev/auth/clerk precisely when setting up the app. The page in question is under <Authenticated></Authenticated>
yeah true, i asked codex to write up this repro when we were debugging. not the best example. but real thing doesn't change in this scenario
I've also tried adding the query to context/top level component and pass the result to those different components to consume instead of querying by themselves, doesn't fix it.
so, you prevented the query while unauthorized here?
because in this case, whether or not there are consumers is irrelevant
without more information about what you're doing, it's hard to say exactly what the issue could be, because it seems like you're misunderstanding something, and not preventing the unauth'ed queries correctly
Here you go - I set up a completely fresh convex starter with:
bun create convex Using package manager: bun ā Choose a client: āŗ React (Vite) ā Choose user authentication: āŗ Clerk (requires Clerk account)I didn't change anything besides adding my VITE_CLERK_PUBLISHABLE_KEY to .env.local and settings up CLERK_JWT_ISSUER_DOMAIN in Convex dashboard. And as you can see in the video, for every refresh there are multiple "listNumbers" queries logged, even though there should be just one.
So I guess it must be an issue with Convex <-> Clerk integration?
The docs for query caching say, "This is for optimizing the user experience, not database bandwidth." My guess is that it also doesn't optimize the function calls. I think the function call needs to happen regardless of whether or not the returned data is cached.
The official starter uses "import { useQuery } from "convex/react";" and not "import { useQuery } from 'convex-helpers/react/cache';", so the issue is not related to the caching helper.
In either case, there are function calls being made from multiple components. From my understanding, the subscription sharing is handled after the function call. You can see the "cached" indicator in the log entry, which I believe is the evidence of that shared subscription, but if there are 10 calls from 10 components all sharing that same subscription, it'll still be billed as 10 function calls. That's my understanding of the system, anyway.
I think you're misunderstanding the issue.
When I noticed this issue, I thought it might be related to multiple components using the same query or cache helper (even though it shouldn't be), because that's what I was personally using at the moment.
But then I decided to spin up a fresh official template, which has only ONE component with ONE useQuery and NO query cache helper - and the issue is still there.
You can try it yourself: npm create convex, choose React (Vite), and choose Clerk for user authentication.
Just FYI, "there are 10 calls from 10 components all sharing that same subscription, it'll still be billed as 10 function calls" this is not true. If you have 10 components in the same view using the same useQuery and args, it results in one subscription and one billable function call for that client, not 10.
Sorry, I was focused too much on your original message and hadn't fully processed what happened since then.
Apologies also for misunderstanding the subscription/function call stuff. I don't often dive into the inner workings of the system, so that was just a hunch.
I wonder if this could be a React render issue and not necessarily related to Convex. Try commenting out the query line in that component, add a console log, and see how many times that logs when reloading the page.
Or maybe leave the query line active and just add the console log.
My gut says that there are multiple renders happening somehow, leading to multiple successive function calls. Just not sure of the actual cause.
Yup, it also logs multiple times
It must be something with auth flow
Maybe comment out all of the Clerk stuff? (just to test the auth flow theory)
If this is indeed an auth flow problem, it'll likely only happen when the app first loads. I don't think that every page/component is going to have the same multi-render issue.
Not an expert in this but thought this might be related - there are 2 very similar-looking calls being made on every refresh. Idk

@Hmza / @deen / whoever, please begin reading here when you check in again
I've replicated this, it does run queries twice if they're subscribed to immediately when auth is confirmed. It seems like the auth state "confirms" twice, which disrupts the established queries, even though the components themselves only render once as expected.
This also happens with Next.js and Clerk, and Vite with Convex Auth. So, maybe this is expected behaviour...
I see... Thanks for checking. So how to address this? Even if it's expected behaviour, it's quite inefficient and feels like it should be solved? Even for a medium size app this can result in hundreds of thousands of additional billed functional calls per month.
I'm not sure if you're part of the Convex team or a mod haha, but is there a way to get an official response from the team?
I'm just a wandering OG. Hopefully someone will comment today, otherwise it's worth opening a support ticket for if you're a pro customer.
Well, I would open one myself anyway.
I'm not sure if you're part of the Convex team or a modConvex team members have the Convex logo badge next to their name. Mods have the mod badge (a red shield with the Convex logo), but we're not staff. While we try to help where we can, our main role is to moderate the server.
is there a way to get an official response from the team?The most reliable way to get input from the Convex team is to open a support ticket. If you have a Pro account, you can do this from the dashboard. Otherwise email
support@convex.devThanks for taking the time and helping out, both of you! š
I planned to have the Pro account when I deploy to prod, so I guess I have to email them until then, right?
Yeah. The Convex team is still pretty small, so support may take a bit for non-Pro accounts.
I'd try it with pnpm package maneger, with nodejs 20 or higher, if that wasn't the case th pure solution to make the called Idempotent API with
useRef and passing a key, and you can check if that key has changed,
second solution would be, to move things to a top level domain, this way the call is called once since every component will render a call
third solution would be to use the useQuery from convex, and I'd recommend passing a "skip" to the queries, so that the functions get called on mount or what ever condition you do, something like useQuery(api.sample.getTimeStamp, isMounted ? {}:"skip")
and see which one fixes your issue, if I had to guess, the first one would fix itThank you, none of these have worked for me though
I've also posted in Clerk support, hopefully they can share their view on this. https://discord.com/channels/856971667393609759/1424661093665603644
I also sent an email to Convex support because I'll only be able to get convex pro account in 2 days, so meanwhile if anyone has a pro acc and would be kind enough to submit a ticket on behalf of me, I would really appreciate it š
You can see this happening with a basic create convex app with Clerk or Convex Auth. I tried everything I could think of to prevent queries from running twice, but it doesn't seem to have anything to do with React renders external to convex, query cache, or number of consumers.
The third option would kind of work, but you would have put your entire app under some mechanism to prevent any queries from occurring within around 1 second of auth being completed. The auth components, useConvexAuth and useAuth from Clerk can't be relied on without a delay.
Hi there, i'm not living on your machines to figure out what the problem is, I just gather what ever thing that could be the cause of it, not a user of clerk, so It might be a dependency issue on their end! who knows!
My Next.js + Convex app does this, I'm not sure when it started but I would have probably ignored it if I'd noticed it, and written it off some quirk in my app or Next, but it exists in the most basic example possible
I'd try SSR. since its a one time thing until the user reloads the page, or the cache ic invalidated..
It would often not be a big deal, but it's still wrong. If you're loading into the middle of complex, personalised page with lots of queries, they will fire twice regardless of SSR
that's a good point
okay, just tested it, and yep this is weird,
1- I've used
useQuery from convex/react and it's getting called three times on the dashboard
2- I've removed the "strictMode" and Its now getting called two times, good progress,
3- I've used the cache provider, and its being called once, so there's something there,Is that with or without Clerk?
with clerk
I don't think clerk is the problem though
I thought it might be, so it's good to hear that your tests are pointing away from it.
if i had to guess, its the json.stringify in the useEffect that's causing it to fumble
react is just way too flimsy
wasn't there an implementation of stable query somewhere?
I think this is something with how the functions on the dashboard are calculated, it doesn't actually get called twice
Hmm, I didn't test this on the starter where it's just 1 query, but in my app I have multiple queries, some components calling same queries. This is what I get for each distinct query:
- with "strictMode": 3 calls on the dashboard - without "strictMode": 2 calls on the dashboard - with cache helper and with "strictMode": 3 calls on the dashboard - with cache helper and without "strictMode": 2 calls on the dashboardEither way, in no case I get just 1 calls on the dashboard, as I should.
yep! something's up with the dashboard
It has something to do with Clerk/auth handling, because if you completely remove Clerk, everything is okay.
if you run a mutation, it runs only once afterwards
when you say remove, what do you mean? can you share code snippets?
Yes, once you're in the app, navigating around and doing mutations, everything is correct. The duplication is only on page refresh.
yep! this is the expected outcome
I don't have much time on my hands, I'll wait for somebody from the convex team to debug it
nor do I have eight hands :hide_the_pain:
I just told claude code to fully remove clerk auth from the starter app, so: deleted ClerkProvider, ConvexProviderWithClerk, and useAuth + removed Authenticated/Unauthenticated gates + removed Convex auth configuration for Clerk in convex/auth.config.ts, etc.
Understood haha, thank you for taking the time that you already did š
I just hope someone from the team sees this
this is also possible!
Another update: It's most certainly Clerk-related, I've tested a new Convex + React app with WorkOS integration, and the there is no query duplication issue on refresh.
By any chance, has anyone already submitted a Pro support ticket for this?
Hi Everyone, there was a ticket created for this. Although it looks like the debugging has occurred in this thread. My initial guess was that it was related to strictMode as well, but is the consensus now that this is an issue that Clerk introduces somehow? Perhaps the Clerk auth provider is doing something incorrectly with full page refreshes? I'll do my best to share this with the team and perhaps we can get a fix.
(After re-reading many of the messages above), this isn't an explicit Convex issue, but rather, it appears the Clerk Convex Auth provider is either not honoring the strict mode or for some reason we can't see double-rendering (which does often occur in react in dev mode), on first page load. It doesn't seem to occur when the component is loaded via another route change.
I think this is correct, (and as Sara so eloquently phrased
react is just way too flimsy) let me know if I'm missing anything.Yep! I'd run the debugger; inside the provider with a few console.logs
Thanks for checking in!
Just to make sure my claim is clear: In prod / strict mode disabled, when using Clerk Convex Auth, every page refresh causes each distinct useQuery to run twice instead of once.
This does not happen in a Convex + React app without Clerk, or in an app with WorkOS-Convex Auth, so it's most certainly due to Clerk Convex Auth.
You might try a deployment test and see if the problem persists. One of Graham's comments above mentioned that double-rendering often occurs in dev mode with React, so perhaps the deployed app won't have that problem.
I did try it, hence "In prod/strict mode disabled" in my claim above...
Sorry. I think my attention latched onto the "strict mode disabled" part because that's what's emphasized.
got it, that is super helpful, perhaps the Clerk library fails to determine if it's in dev. Thank you for all the data, I'll see what I can do!
You may have figured this out already, but it also happens with Next.js and Convex Auth in production. You can easily observe it by running
npm create convex@latest, create that configuration, build, start.
My memory is fuzzy but I'm pretty sure I observed this happening in my app after the Clerk adapter was updated to work with Next.js 15 which introduced async cookies etc. late last year (it feels like 5 years?). I think I assumed this was a minor bug with my set up. Convex Auth was being actively developed around the same time.
Any updates on this?