Skip to main content

Error Handling

Handle query, mutation, action, auth, upload, and server failures with one public error model.

Handle errors at the boundary that can make the next decision.

Choose the control flow

OperationNormal pattern
QueryRender reactive error and offer refresh() where safe
Form mutation/actionUse .safe() and map structured domain errors
Exceptional mutation/actionCall directly and catch ConvexCallError
Auth operationInspect Better Auth operation result plus reactive auth error
UploadRender upload error; retry with a new explicit user action
Server routeCatch and map to a safe HTTP contract

Query retry

vue
<section v-if="error">
  <p>Projects are unavailable.</p>
  <button @click="refresh">Try again</button>
</section>

Do not retry a required query while auth is anonymous. Let auth state change first.

Structured server errors

ts
const result = await archiveProject.safe({ projectId })

if (!result.ok && result.error.kind === 'server') {
  const data = result.error.data as { code?: string }
  if (data.code === 'PROJECT_ALREADY_ARCHIVED') {
    await navigateTo('/archive')
  }
}

Use structured data.code, not message substrings.

Error boundaries

Vue/Nuxt error boundaries are appropriate for render failures or page-level inability to continue. A failed button action usually belongs inline and should not replace the whole page.

Logging

Log the category, operation name, and safe correlation context. Do not log tokens, cookies, auth headers, raw upload bytes, or error.cause.

Retry safety

Queries are safe to repeat. Mutations and actions may have committed before a network or identity-transition failure became visible. Add backend idempotency where retries are possible.