Noah
Noah•5mo ago

Typescript performance issues with caused by Convex types

We've been having some issues with the Typescript language server caused by convex/_generated. After each edit in VScode it takes around 6 seconds for tsserver to have type info again. When I delete the _generated folder the issue is resvoled. But that also disables all Convex type hints. Have other large Convex codebases encountered the same issue? I can send over tsc perf traces in private if needed.
24 Replies
Convex Bot
Convex Bot•5mo ago
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!
djbalin
djbalin•4mo ago
I am experiencing some very very intense TS performance issues as well, especially in my frontend React files but to a slightly lesser extent also ini my Convex files. I'm in a monorepo with two react native/expo frontend app. I tried analyzing my tsserver logs where I saw type resolution steps (i.e. IDE intellisense) taking >5 seconds, but couldn't make much sense of the trace - I passed it to an AI and it suggested that Convex was creating some very large type graphs that were slowing down the TS server/intellisense. I haven't gotten any closer to solving it either, but would LOVE to hear advice - I like to think that I almost have a monk's anger control, but the 5-10 seconds intellisense times have driven me to banging my fists on the table 😆 I can confirm your finding! Deleting the _generated folder gives me extra virgin intellisense speed in TS stuff 😻
But yeah, this is not a solution, since it breaks types on all Convex-stuff in both backend and frontend files For further information, I have typed all Convex handler functions, e.g. handler: async (ctx, args): Promise<void>. I think this improved the problem slightly, but I may just have been wishfully hallucinating - it wasn't anywhere near enough, at least. I would like to try specifying returns: in all my convex function definitions, but im finding it cumbersome to work with validators for complex return types did you figure out anything @Noah ?
Doogibo
Doogibo•4mo ago
We are experiencing the same thing, and trying to figure out the best way to resolve. It kind of crept up on us and now it's very slow. Typescript Intellisense can take quite some time which hurts momentum. If you haven't already seen this: https://github.com/microsoft/Typescript/wiki/Performance There is a lot to that document and I'm still digesting it, but they have a diagnostic tool you can run before/after your changes to gauge improvements. Also ensure you don't have any circular dependencies as those create unnecessary cycles. A good first step. npx madge --circular --extensions ts,tsx src/ at your convex root. Or at your project root as well as you may have introduced some across application components. skipLibCheck set to true in tsconfig can help, but read the caveat in that doc. From there, I'm still trying to decide the best approach. Know that inference is always slower than explicit typing, for complex types, prefer extended interfaces over combined types. When we started, we used Doc<, Id<, z.infer, no explicit return types basically everywhere. Which I'd prefer to keep, as it's a bit of a pain to explicitly type all of these things as an alternative, and I'm still not sure how much it will boost performance. If using vscode, bump up tsserver memory in your workspace settings: "typescript.tsserver.maxTsServerMemory". Smaller files are better, extract helpers. If using a monorepo, you can extract things to separate packages, introduce a build step for them and import them using package imports (i.e. in pnpm, "@mm/shared": "workspace:*"). Especially for things that won't change very often. But here again you'll have to be careful not to introduce circular dependencies. Be thoughtful about how you organize things. Once you do that, only open and work in smaller scoped workspaces, like /convex, or apps/frontend, whatever. Can anyone from the convex team weigh in? Especially re: the idea of reducing usage of Doc<, Id<, z.infer?
GitHub
Performance
TypeScript is a superset of JavaScript that compiles to clean JavaScript output. - microsoft/TypeScript
ampp
ampp•4mo ago
I spent a lot of time asking about this in the past, buying the fastest computer out there was my solution and switching to native Linux. My problem is lately its hard to tell where one utility ends and another starts. Like with cursor or windsurf aren't they augmenting the intelisense? I feel like recently i benefited from biome as having the formatter lag was delaying the typescript inference. I'm also curious at what point will the rust version of typescript be ready. I was wondering if anyone tried to use it on a convex project.
Doogibo
Doogibo•4mo ago
Yes I deal with copilot fighting with my TS Intellisense. It's a huge pain. In VSCode they do have an aesthetic difference so I can tell which is which, but the timing (copilot is faster at this point, but wrong half the time) throws me off. Do you mean the Go rewrite? I'm kind of hoping to properly resolve as many of the issues I can before that .... hides them all. 😛
ampp
ampp•4mo ago
yeah, go? whatever recently announced. If we could benchmark a project then we would know if strongly typing everything is ok, i use infer and objectType over 100 times on arrays of literals. I've not done it as i have 100s of functions to edit for a unknown amount of performance
ballingt
ballingt•4mo ago
I'm just finding this thread now via a support email. Anyone with this promblem might consider static code generation: https://docs.convex.dev/production/project-configuration#using-static-code-generation-beta It's a different way to work, you'll need to use arg and return value validators. And it's not perfect yet. But this is what some large customers are using to deal with this issue. We know about these typescript issues and have been looking into potential solutions, but the big hammer of static generation of some sort is the endgame. We may be able to make our types faster and typescript-go is going to help once it's out, but these will never be faster than generated code. Likely the best we'll be able to do is to increase the number of convex/ directory endpoint files at which point this becomes unwieldy by some multiple, but it's likely our largest customers will fine the tradeoff of static types makes sense for them.
adam
adam•4mo ago
Hopefully the Typescript rewrite in Go will solve the slow type inference. I have noticed improvements by closing files in my IDE that I am not activly working on, in particular large Convex .ts files. I'm also breaking up large files in my /convex folder to try help with this issue.
adam
adam•4mo ago
I can't seem to get the static code gen to work @Tom. This is my convex.json file that is in same dir as my package.json.
{
"$schema": "https://raw.githubusercontent.com/get-convex/convex-backend/refs/heads/main/npm-packages/convex/schemas/convex.schema.json",
"codegen": {
"staticApi": true,
"staticDataModel": true
}
}
{
"$schema": "https://raw.githubusercontent.com/get-convex/convex-backend/refs/heads/main/npm-packages/convex/schemas/convex.schema.json",
"codegen": {
"staticApi": true,
"staticDataModel": true
}
}
I do get an IDE warning with and without including the "$schema" as codegen doesn't seem to be defined in the schema. Should I expect the types to generate when I run convex dev?
No description
ian
ian•3mo ago
Even if codegen isn't in the schema (it's beta) - it should still work if you can get it to build without that warning
adam
adam•2mo ago
I got the static code gen working now. It may have been because theconvex.json was inside the convex directory, not inside the the convex parent directory. @ian using static gen has caused type issues with the onComplete handler of the retrier component. Type string is not assignable to type RunId. Also impacts the workflow component onComplete too I'd assume. It seems the onCompleteValidator from '@convex-dev/action-retrier' is expecting RunId type, though the static gen has string type.
await retrier.run(
...,
{
onComplete: ... _callback, // <= Type issue
},
);

export const _callback = internalMutation({
args: onCompleteValidator, // <= !
...
});
await retrier.run(
...,
{
onComplete: ... _callback, // <= Type issue
},
);

export const _callback = internalMutation({
args: onCompleteValidator, // <= !
...
});
// convex/_generated/api.d.ts
_callback: FunctionReference<
"mutation",
"internal",
{
result:
| { returnValue: any; type: "success" }
| { error: string; type: "failed" }
| { type: "canceled" };
runId: string; // <= !
},
any
>;
// convex/_generated/api.d.ts
_callback: FunctionReference<
"mutation",
"internal",
{
result:
| { returnValue: any; type: "success" }
| { error: string; type: "failed" }
| { type: "canceled" };
runId: string; // <= !
},
any
>;
In addition to this type issue caused by using static gen, it would be very helpful to have a Zod compatible onCompleteValidator that can be used with custom mutation using Zod.
ian
ian•2mo ago
Interesting - yeah the static code gen is going to strip any branded types (they're just strings under the hood). So you will have to pass them in as strings (maybe cast with as RunOptions["onComplete"]) unless one of us comes up with a clever workaround. For Zod, I'm assuming you're only talking about the workflow component's context arg? Since the types otherwise are not very useful to be done in zod? e.g. you can do args: onCompleteValidator for the retrier args. And this is for your own code returning an object that you don't trust to pass through the more specific type? You could do the zod validation before you enqueue it at all, so you fail fast, then do zodToConvex in the onComplete to get the basic validator & types?
adam
adam•2mo ago
Since Convex doesn't do runtime validation on component IDs, do these branded types add much value? I ran into the same thing with my components and found it simpler to just pass string across the convex => component boundary. Then inside my component I do a zid() runtime validation to check the ID. What are your thoughts on just using string for component IDs in general? Yep, using onComplete: internal.onComplete as RunOptions["onComplete"] works for solving the RunId type issue caused by using static code gen - thanks. Even still, with or without using static code gen, I can't find a good solution to the following: This code works fine:
export const onComplete = internalMutation({
args: onCompleteValidator,
handler: async (ctx, args) => {...}
export const onComplete = internalMutation({
args: onCompleteValidator,
handler: async (ctx, args) => {...}
However, what I would like to do is use my Zod based custom mutation (that expects Zod args, not Convex args), Like so:
export const onComplete = zInternalMutation({ // <= zInternalMutation being my Custom Zod function
args: onCompleteValidator, // <= Uses Convex validators, not Zod
handler: async (ctx, args) => {...}
export const onComplete = zInternalMutation({ // <= zInternalMutation being my Custom Zod function
args: onCompleteValidator, // <= Uses Convex validators, not Zod
handler: async (ctx, args) => {...}
I thought maybe I could use convexToZod here, but it doesn't work.
export const onComplete = zInternalMutation({
args: convexToZod(onCompleteValidator)
handler: async (ctx, args) => {...}
export const onComplete = zInternalMutation({
args: convexToZod(onCompleteValidator)
handler: async (ctx, args) => {...}
If you have any ideas or pointers that would be great, thanks.
ian
ian•2mo ago
interesting that the convexToZod didn't work - not sure why that would be.. worth investigating in case it's an easy fix. You can also define args that just match the onComplete validator:
{
workId: z.string() as unknown as z.ZodType<string & {_: "foo"}, z.ZodStringDef, "hi">,
runResult: z.union([
z.object({
type: z.literal("success"),
returnValue: z.any(),
}),
z.object({
type: z.literal("failed"),
error: z.string(),
}),
z.object({
type: z.literal("canceled"),
}),
])}
{
workId: z.string() as unknown as z.ZodType<string & {_: "foo"}, z.ZodStringDef, "hi">,
runResult: z.union([
z.object({
type: z.literal("success"),
returnValue: z.any(),
}),
z.object({
type: z.literal("failed"),
error: z.string(),
}),
z.object({
type: z.literal("canceled"),
}),
])}
adam
adam•2mo ago
I think I tried this too the other day. I have this noted about this approach: // The issue here is that z.any() maps to returnValue?: any but need to be returnValue: any (Convex type). // Potential works fine in Zod v4 - https://github.com/colinhacks/zod/issues/1628#issuecomment-2856581024
ian
ian•2mo ago
ah yeah - wanting a required (non-undefined) version of z.any() - like z.any().required() - or for the convex type to be v.optional(v.any())
deen
deen•2mo ago
From my experience with TS slowness in the past with a "medium" sized convex ents app, the biggest culprit by far is zod (v3). You can introduce observable slowdown easily (I guess depending on your CPU) by introducing zod-derived types into your data model, when using discriminated unions and/or merge/pick/omit chains - both things I love using. They have horrific Typescript performance. tRPC apps famously suffer from this issue. After I made peace with having partially duplicated schemas and refactored all table schema/args validators back to vanilla, reasonable Typescript performance was restored. For me, it was worth it. zod 4 (which is now "officially" released https://github.com/colinhacks/zod/releases/tag/v4.0.1) is supposed to improve performance, but I'd still be hesitant to use it for table definitions, or anything that could have a large blast radius.
deen
deen•2mo ago
Interestingly, valibot's basic schema validators are almost identical to convex https://valibot.dev/guides/schemas/
Schemas
Schemas allow you to validate a specific data type. They are similar to type definitions in TypeScript.
ampp
ampp•2mo ago
Theo - t3․gg
YouTube
I spent $4,000 to make TypeScript faster
TypeScript's language server performance is rough, luckily it's finally getting fixed. Can't wait for TypeScript 5.9! Thank you Arcjet for sponsoring! Check them out at: https://soydev.link/arcjet SOURCES https://devblogs.microsoft.com/typescript/announcing-typescript-5-9-beta/ https://www.youtube.com/watch?v=AEA0K77qhS4&t=297s Want to sponso...
ampp
ampp•2mo ago
have you been using zod4? edit: looks like it we are waiting on some pr's in helpers.
ian
ian•2mo ago
Yeah someone started a PR but I think it was mostly AI-generated and I fear is going to need someone to start over - @SamB put in a heroic effort I think, but I don't know where that ended up
deen
deen•2mo ago
I've haven't used zod validators in convex since then. I've used zod4 but not for anything crazy yet. LLMs have made managing verbose/duplicated definitions much less of an issue. The zod4 API is pretty much the same on the surface, but the underlying types have changed. From memory the zod helper basically just looks at what type of validator you're using and converts between them (recursively). It's probably easier than it use to be.
SamB
SamB•4w ago
Just put up the PR! Like you predicted, transforms were a can of worms. Took a lot of actual in-app testing to work out what approach made actual real sense! https://github.com/get-convex/convex-helpers/pull/701
GitHub
Zod V4 implementation by SamJB123 · Pull Request #701 · get-conve...
Ready for testing and refinement by community - but expect rough edges! Current version includes several opinionated decisions with a goal of a schema once. Uses ZodV4 internals to map Zod&amp;#39...
SamB
SamB•4w ago
It's definitely going to have bugs etc, but it builds and is working well in my personal stuff. Yay. I included a react component for in-app testing in the PR.

Did you find this page helpful?