---
title: "Troubleshooting"
description: "Every warning and error the module can print, what causes it, and the fix."
canonical_url: "https://nuxt-convex-module.dev/getting-started/troubleshooting"
---
# Troubleshooting

> Every warning and error the module can print, what causes it, and the fix.

Every message the module prints, in the order you are likely to meet it: at build time, when the deployment URL is missing at runtime, in the data composables, in Better Auth, and in file uploads. Each entry quotes the message as printed (`…` stands for the value the module fills in), says where it appears, and gives the cause and the fix. Two failures that come from the Convex CLI and from Node, not from the module, close the page.

## What a healthy `nuxt dev` looks like

The module logs one line at startup — the resolved deployment URL, the functions directory, and which integrations are on:

```sh
ℹ Convex https://your-deployment.convex.cloud · functions: convex/ · integrations: better-auth, nuxt-security
```

Before the first `npx convex dev` it also says, once:

```text
Convex codegen not found in `…/_generated` — run `npx convex dev`. Convex features no-op until it exists.
```

and, the moment codegen lands:

```text
Convex codegen detected — generated `api` wired app-wide.
```

Neither needs anything from you. The `any`-typed placeholders and the no-op `api` plugin that cover the gap are described under [before codegen runs](https://nuxt-convex-module.dev/getting-started/configuration#before-codegen-runs).

The very first run in an app whose `dev` script is still the plain `nuxt dev` also says, once:

```text
Set `scripts.dev` to `…` — one command now runs Convex beside Nuxt, no .env needed locally. Set `convex.devScript: false` to keep your own script.
```

`package.json` now reads `"dev": "convex dev --start \"nuxt dev\""`; restart with it: the Convex CLI writes the deployment URL to `.env.local`, and the module reads it from there. Details under [run](https://nuxt-convex-module.dev/getting-started/installation#run).

When an AI coding agent runs Nuxt (Claude Code, Codex, Cursor, Gemini CLI, or anything that sets `AI_AGENT`), the first of these notices is followed, once, by the commands that work without a terminal to answer prompts:

```text
An AI agent is running Nuxt. `CONVEX_AGENT_MODE=anonymous npx convex dev --once` sets up a local Convex deployment with no prompts and no account, writes .env.local and the codegen, then exits. For the setup steps and this module's API: `npx skills add https://nuxt-convex-module.dev --skill nuxt-convex-module -y`.
```

See [AI Agents](https://nuxt-convex-module.dev/getting-started/ai-agents).

## Build and configuration

These come from the `[nuxt-convex-module]` logger when Nuxt starts. An *error* here does not stop the build; it tells you the app will not work as configured.

### No deployment URL

```text
No Convex deployment URL configured. Set NUXT_PUBLIC_CONVEX_URL or `convex.url` in nuxt.config, or run `npx convex dev`: it writes CONVEX_URL to .env.local, which the module reads in development.
```

Nothing named a deployment: no `convex.url`, no `NUXT_PUBLIC_CONVEX_URL`, no `CONVEX_URL` in the environment Nuxt loaded, and in development none in `.env.local` either. In development, run `npx convex dev` (or `pnpm dev`, which starts it). A build does not read `.env.local`: give it `NUXT_PUBLIC_CONVEX_URL`, in `.env` or the environment. The client is not created until this is fixed — see [the runtime twin](#no-deployment-url-at-runtime) below.

### Swapped `.cloud` and `.site` URLs

```text
`convex.url` ("…") ends with .convex.site, which is the HTTP Actions domain — deployment URLs end with .convex.cloud. Did you mean to set `convex.siteUrl`?
```

```text
`convex.siteUrl` ("…") ends with .convex.cloud, which is the deployment domain — site URLs (HTTP Actions) end with .convex.site. Did you swap it with `convex.url`?
```

The two Convex origins are in each other's slot. `url` (`NUXT_PUBLIC_CONVEX_URL`) is the deployment, `.convex.cloud`; `siteUrl` (`NUXT_PUBLIC_CONVEX_SITE_URL`) is HTTP Actions, `.convex.site`. Swap them back.

### A URL that does not parse

```text
`convex.url` ("…") does not look like a valid http(s) URL — Convex clients will fail to connect.
```

```text
`convex.siteUrl` ("…") does not look like a valid http(s) URL.
```

The value has no `http://` / `https://` scheme, or does not parse as a URL at all — typically a copy-paste that lost the scheme or gained a character. Trailing slashes are fine; the module strips them.

### `authRoute` problems

```text
`convex.authRoute` ("…") must start with "/" — using "…".
```

A leading slash was added for you. Fix the option to silence the warning. A trailing slash is stripped silently.

```text
`convex.authRoute` is "…" but the bundled Better Auth client still calls /api/auth, so every auth request would 404. Keep the default route, or set `convex.betterAuth.authClient` to a client created with `basePath: "…"`.
```

The proxy moved, the client did not: the bundled Better Auth client is created with Better Auth's default `basePath`, `/api/auth`. Either keep the default route, or [bring your own auth client](https://nuxt-convex-module.dev/components/better-auth#bring-your-own-auth-client) created with `createAuthClient({ basePath: '/your/route', … })`.

### A custom auth client that is not there

```text
`convex.betterAuth.authClient` points at "…", which does not exist (resolved against `…`). The build would fail with an opaque import error — fix the path or remove the option to use the bundled client.
```

The path is resolved against the project root (`rootDir`), not `app/`. `./app/convex-auth-client` is the usual spelling; the extension may be left off.

### An integration turned on by hand, with its package missing

```text
`convex.…` is enabled but `…` is not installed. Run `npm install …` or remove the option.
```

You set `betterAuth: true` (or `clerk`, `auth0`, `polar`, `security`) and the package does not resolve. Install it, or drop the option and let auto-detection decide.

### Two auth adapters at once

```text
Better Auth and … are both enabled. Better Auth's plugin owns the Convex client's auth, so the … adapter would fight it — set …, or `convex.betterAuth: false`, to pick one.
```

`@convex-dev/better-auth` and `@clerk/vue` (or `@auth0/auth0-vue`) are both dependencies, so both integrations came on. Better Auth's plugin registers auth on the Convex client; the Clerk / Auth0 adapter would register a second time on the same client. Turn one off with the option the message names.

### Polar without its checkout peer

```text
`@convex-dev/polar` is installed but its peer `@polar-sh/checkout` is not — `<CheckoutLink>` imports it and the build fails the moment the component is used. Run `npm install @polar-sh/checkout`.
```

npm, pnpm and bun install the peer automatically; yarn and `--legacy-peer-deps` do not. Install it.

### `convex.json` does not parse

```text
Failed to parse convex.json: …
```

The file is not valid JSON. The module falls back to the default `convex/` functions directory until it is fixed.

## No deployment URL at runtime

The build-time warning above has runtime counterparts, because a built app can start without the variable the build had — or the other way round.

### The client was not created

```text
[nuxt-convex-module] No Convex deployment URL configured — client not created. Set NUXT_PUBLIC_CONVEX_URL or `convex.url` in nuxt.config.
```

```text
[nuxt-convex-module] No Convex deployment URL configured — client and auth not created. Set NUXT_PUBLIC_CONVEX_URL or `convex.url` in nuxt.config.
```

Printed in the browser console by the client plugin (the second form with Better Auth on). `runtimeConfig.public.convex.url` is empty at runtime: the build had no URL and the deployment did not set `NUXT_PUBLIC_CONVEX_URL` either. Set the variable where the app runs. The next composable call hits the error below.

### A composable could not find the client

```text
Could not find Convex client! `…` must be used in the Vue component tree where the Convex client has been provided — in Nuxt, install the `nuxt-convex-module` module (or `provide(ConvexClientKey, client)`). Did you forget it? See https://docs.convex.dev/quick-start
```

Thrown at component setup, naming the composable. Three causes: the client was never created (the warning above — fix the URL); the composable ran outside component setup, such as in a Nitro route, where the [server helpers](https://nuxt-convex-module.dev/guide/server-and-ssr) are the right tool; or, in a [plain Vue app](https://nuxt-convex-module.dev/guide/plain-vue), `app.provide(ConvexClientKey, client)` is missing.

### `useAsyncQuery` could not fetch

```text
`useAsyncQuery` could not fetch during SSR: no Convex deployment URL is configured. Set NUXT_PUBLIC_CONVEX_URL or `convex.url` in nuxt.config.
```

```text
`useAsyncQuery` could not fetch on the client: no Convex client is available. Set NUXT_PUBLIC_CONVEX_URL or `convex.url` in nuxt.config.
```

```text
`useAsyncPaginatedQuery` could not fetch during SSR: no Convex deployment URL is configured. Set NUXT_PUBLIC_CONVEX_URL or `convex.url` in nuxt.config.
```

```text
`useAsyncPaginatedQuery` could not start on the client: no Convex client is available. Set NUXT_PUBLIC_CONVEX_URL or `convex.url` in nuxt.config.
```

Same root cause as above. These land in the composable's `error` ref (`status === 'error'` for `useAsyncQuery`); they are not thrown. For `useAsyncPaginatedQuery` the hydrated server page, if any, stays on screen while `error` says why nothing is live.

### The server helpers have no URL

```text
Environment variable NUXT_PUBLIC_CONVEX_URL is not set.
```

```text
Convex function called with invalid deployment address.
```

Thrown by `fetchQuery` / `fetchMutation` / `fetchAction` / `preloadQuery`. The first: runtime config is empty on the server and the environment has no `NUXT_PUBLIC_CONVEX_URL` — set it in the deployment. The second: you passed `{ url: '' }` or another non-string yourself — pass a URL, or leave `url` out.

```text
deploymentUrl is undefined, are your environment variables set? In the future explicitly passing undefined will cause an error. To explicitly use the default, pass `process.env.NUXT_PUBLIC_CONVEX_URL`.
```

Logged with `console.error` when you pass `{ url: undefined }` explicitly. Drop the key.

### The server helpers reject the URL

```text
Invalid deployment address: Must start with "https://" or "http://". Found "…".
```

```text
Invalid deployment address: "…" is not a valid URL. If you believe this URL is correct, use the `skipConvexDeploymentUrlCheck` option to bypass this.
```

```text
Invalid deployment address: "…" ends with .convex.site, which is used for HTTP Actions. Convex deployment URLs typically end with .convex.cloud? If you believe this URL is correct, use the `skipConvexDeploymentUrlCheck` option to bypass this.
```

The URL the server helpers received fails the same checks the build runs. Fix the value; for a self-hosted deployment whose URL is genuinely unusual, pass `{ skipConvexDeploymentUrlCheck: true }`.

### Plain Vue: the client constructor

```text
No address provided to ConvexVueClient. If trying to deploy to production, make sure to follow all the instructions found at https://docs.convex.dev/production/hosting/ If running locally, make sure to run `convex dev` and ensure the .env.local file is populated.
```

```text
ConvexVueClient requires a URL like 'https://happy-otter-123.convex.cloud', received something of type … instead.
```

```text
Provided address was not an absolute URL.
```

Only reachable when you construct `ConvexVueClient` yourself. `import.meta.env.VITE_CONVEX_URL` (or whatever you read) is unset, not a string, or not absolute.

## Data composables

### A Convex function used as an event handler

```text
Convex function called with Event object. Did you use a Convex function as an event handler directly? Event handlers like @click receive an event object as their first argument. These Event objects are not valid Convex values. Try wrapping the function like `const handler = () => myMutation();` and using `handler` in the event handler.
```

`@click="send"` passes the `MouseEvent` as the mutation's argument. Wrap it: `@click="() => send({ … })"`.

### Optimistic update attached twice

```text
Already specified optimistic update for mutation …
```

`.withOptimisticUpdate()` was called on a mutation that already had one — usually once per call instead of once per `useMutation`. Attach it once, where the mutation is created.

### `initialNumItems` missing or invalid

```text
`options.initialNumItems` must be a positive number. Received `…`.
```

`usePaginatedQuery`'s third argument is required and must be `{ initialNumItems: <positive number> }`. A misspelled key reads as `undefined`.

### Pagination reset

```text
usePaginatedQuery hit error, resetting pagination state: …
```

A warning, not an error. The server rejected a stored cursor — the query's definition or index changed under it. The composable restarts from the first page. If it repeats, the query's args are changing under a cursor that no longer matches them.

### Not a paginated query

```text
Paginated query returned a non-pagination result.
```

The query passed to `usePaginatedQuery` does not take `paginationOpts` and return `{ page, isDone, continueCursor }`. Before codegen runs, `#convex/api` is typed `any`, so the type check cannot catch this. Use `useQuery` for it, or paginate the query.

### No auth context

```text
Could not find Convex auth context. Ensure `provideConvexAuth` has been called in an ancestor component or that the Convex auth plugin is installed, or you might have two instances of the `nuxt-convex-module` runtime loaded in your project.
```

`useConvexAuth()` or one of `<Authenticated>` / `<Unauthenticated>` / `<AuthLoading>` / `<AuthRefreshing>` ran with no provider above it: a Nuxt app with no auth integration (the base client plugin provides none), a Clerk / Auth0 adapter called below the consumer instead of above it, or a plain Vue app without `provideConvexAuth()`. The "two instances" case is a hoisting problem — two copies of the runtime resolved from different `node_modules`.

### Plain Vue: client misuse

```text
ConvexVueClient has already been closed.
```

A query or mutation ran after `client.close()`.

```text
Passing a string to ConvexVueClient.setAuth is no longer supported, please upgrade to passing in an async function to handle reauthentication.
```

`setAuth` takes a token *fetcher* — `async () => token` — so it can re-fetch when a token expires.

## Better Auth

### The site URL is missing or wrong

```text
NUXT_PUBLIC_CONVEX_SITE_URL is not set. This is automatically set in the Convex deployment, but must be set in the Nuxt environment. For local development, this can be set in the .env file, or via the `convex.siteUrl` option in nuxt.config.
```

```text
NUXT_PUBLIC_CONVEX_SITE_URL should be set to your Convex Site URL, which ends in .convex.site. Currently set to ….
```

```text
NUXT_PUBLIC_CONVEX_SITE_URL must be an absolute URL, including the scheme. Currently set to ….
```

```text
NUXT_PUBLIC_CONVEX_SITE_URL must use http: or https:. Currently set to ….
```

Thrown by `convexAuth(event)` — which is also what the `/api/auth/**` proxy calls, so a proxied request answers 500 with the same text. The Better Auth endpoints live on the `.convex.site` origin, and it has to reach the server as `runtimeConfig.convex.siteUrl` or `runtimeConfig.public.convex.siteUrl`: `convex.siteUrl` in `nuxt.config`, or `NUXT_PUBLIC_CONVEX_SITE_URL` in the environment (at build time, or set where the built app runs).

### The proxy answered 400 with no message

The `/api/auth/**` proxy refuses a request whose path changes under URL normalisation — `/api/auth/../../x` and the like — with a bare `400`. Nitro routes the raw path, so without the check a traversal would still match the route. A real client never sends one; if you see it, something upstream of the app rewrote the path. Details under [the Better Auth proxy](https://nuxt-convex-module.dev/getting-started/security#the-better-auth-proxy).

### Token prefetch failed

```text
[nuxt-convex-module] Failed to prefetch auth token for SSR: …
```

A server-side warning; the page still renders, signed out. The text after the colon is the cause: one of the site-URL errors above, an unreachable deployment, or Better Auth's routes not mounted in your `convex/http.ts`.

### A one-time token was ignored

```text
[nuxt-convex-module] Ignoring cross-domain one-time token on "…" — consumption is restricted to "…" (convex.betterAuth.crossDomainCallbackRoute).
```

The page received `?ott=` on a route other than `crossDomainCallbackRoute`. The token is scrubbed from the URL without being exchanged — the point of the option. Point every `callbackURL` in your sign-in calls at the callback route, or widen the option.

### A one-time token was exchanged with no callback route

```text
[nuxt-convex-module] Exchanged a cross-domain one-time token on "…" with no `convex.betterAuth.crossDomainCallbackRoute` set — any page can complete sign-in. Restrict it to your callback route.
```

Dev only. Sign-in completed, on whatever page received the `?ott=` link — upstream's default. Set `crossDomainCallbackRoute` to the one route your sign-in `callbackURL`s point at; see [cross-domain one-time tokens](https://nuxt-convex-module.dev/getting-started/security#cross-domain-one-time-tokens).

### A one-time token could not be exchanged

```text
[nuxt-convex-module] Failed to consume cross-domain one-time token:
```

The exchange call failed: the token expired (they live about three minutes), was already used, or the site URL is misconfigured (above). The user lands signed out; sign in again.

## File uploads

### Upload attempted during SSR

```text
uploadFile can only run in the browser (no XMLHttpRequest available).
```

```text
useUpload().upload() can only run in the browser.
```

The upload ran during server rendering. Call it from an event handler or `onMounted`, never at setup.

### Convex rejected the upload

```text
Convex upload failed with status …: …
```

Upload URLs are single-use and expire after about an hour. Generate a fresh one per attempt — the composables do; with `uploadFile()` you do it yourself.

```text
Convex upload returned an unexpected response: …
```

`url` was not a Convex upload URL — the response had no `storageId`. Use the URL your `generateUploadUrl` mutation returns.

```text
Network error during Convex file upload.
```

The request never completed: offline, or — in production — a Content Security Policy that does not allow the deployment origin in `connect-src`. The module's [CSP integration](https://nuxt-convex-module.dev/getting-started/security#content-security-policy) adds it when `nuxt-security` is installed.

A cancelled upload rejects with an `AbortError` (`Upload aborted.`); that is the result of `cancel()`, not a failure.

## From the Convex CLI and Node

### A local backend is still running

```sh
✖ A local backend is still running on port 3210. Please stop it and run this command again.
```

The Convex CLI prints this when `dev` starts while a local deployment from an earlier run still holds its port. That run was stopped with SIGTERM, a plain `kill`, which is how many agents and process managers stop a process: `convex dev` exits on it but leaves the local backend and the Nuxt server running. Stop what that run left behind, the backend on the port named in the message and the Nuxt server, then run `dev` again. On macOS and Linux, with the default ports: `kill $(lsof -ti tcp:3210,3000 -sTCP:LISTEN)`. On Windows, end `convex-local-backend.exe` and the old `node` process in Task Manager. Ctrl-C, which sends SIGINT, stops all three.

### The built server cannot find `convex`

```sh
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '…/convex/dist/esm/index.js' imported from …/.output/server/chunks/virtual/entry.mjs
```

A built app answers every request with 500 and logs this, on a Node server or in a Vercel function, while `nuxt dev` works. Nuxt 4.6 changed how a production build resolves the imports of a module's runtime, and module versions before 0.11 let it inline the Convex client with an import that points outside the build output. Update `nuxt-convex-module`. On 0.10, adding `'convex'` to `vite.resolve.dedupe` in `nuxt.config.ts` fixes it too, next to any packages already listed there:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  vite: { resolve: { dedupe: ['convex'] } },
})
```


## Sitemap

See the full [sitemap](https://nuxt-convex-module.dev/sitemap.md) for all pages.
