Plain Vue and Vite
Use the shared Better Convex client lifecycle without Nuxt, Nitro, or Better Auth.
@lupinum/better-convex-vue is the browser package shared by plain Vue applications
and Better Convex Nuxt after hydration. It provides one identity-safe lifecycle
for queries, pagination, mutations, actions, connection state, and cleanup.
The current beta is 0.8.0-beta.40. It has no Nuxt, Nitro, H3, or Better Auth
dependency. Nuxt remains the full-stack package for SSR, server calls, the auth
proxy, route middleware, and deployment-owned Better Auth integration.
Install
pnpm add @lupinum/better-convex-vue@0.8.0-beta.40 convex@1.42.2 vue@^3.5.0Anonymous application
Install one plugin at the Vue application root:
import { createApp } from 'vue'
import { createBetterConvex } from '@lupinum/better-convex-vue'
import App from './App.vue'
const app = createApp(App)
app.use(
createBetterConvex({
convexUrl: import.meta.env.VITE_CONVEX_URL,
}),
)
app.mount('#app')Then use synchronous Vue composables. State begins pending and updates through refs; composable creation itself is not awaited:
<script setup lang="ts">
import { api } from '../../convex/_generated/api'
import { useConvexMutation, useConvexQuery } from '@lupinum/better-convex-vue'
const { data: notes, status, error } = useConvexQuery(api.notes.list, {})
const renameNote = useConvexMutation(api.notes.rename)
</script>
<template>
<p v-if="status === 'pending'">Loading…</p>
<p v-else-if="error">Could not load notes.</p>
<ul v-else>
<li v-for="note in notes" :key="note._id">
<button @click="renameNote({ id: note._id, title: `${note.title}!` })">
{{ note.title }}
</button>
</li>
</ul>
</template>Queries with an exactly empty validator may omit arguments. Every query with a
declared key receives an explicit argument object or the literal 'skip'.
Use a computed argument that returns 'skip' when reactive execution policy
denies the query. Skipping a protected query retires its listener and data.
Query data is undefined until a value exists. A backend null result is
successful data, not a loading or skip sentinel.
Provider-neutral authentication
The Vue package does not select an identity provider. An auth adapter owns its provider SDK, observes session transitions, and supplies a short-lived Convex token:
import type { BetterConvexAuthAdapter } from '@lupinum/better-convex-vue'
const auth: BetterConvexAuthAdapter = {
snapshot: () => ({
status: session.value.status,
identityKey: session.value.subject,
sessionGeneration: session.value.generation,
error: session.value.error,
}),
subscribe: session.subscribe,
fetchToken: async () => await fetchConvexTokenFromSameOriginBackend(),
refreshSession: async () => await session.value.refetch(),
}identityKey is a stable, non-secret subject used only to partition browser
state. sessionGeneration advances whenever credentials are replaced or
revoked, including a new session for the same subject. It is not a role,
permission, access token, or authorization decision.
Install the adapter with the plugin:
app.use(
createBetterConvex({
convexUrl: import.meta.env.VITE_CONVEX_URL,
auth,
}),
)The application backend remains responsible for issuing the Convex token. Never place provider secrets, cookies, refresh tokens, deployment keys, or server signing keys in the Vite bundle. Every protected Convex function must reload current application authority for its effect.
Embedded Vue applications
A Nuxt host can expose useConvexAttachment() and pass that opaque frozen
attachment unchanged to an embedded Vue root:
const attachment = hostBridge.convexAttachment
embeddedApp.use(createBetterConvex({ attachment }))The attachment contains a stable, restricted call/subscription handle and identity lifecycle. It does not contain a JWT, cookie, token refresh function, raw replaceable Convex client, or Better Auth session.
Use one runtime owner. Do not install a second standalone client beside an attachment or forward credentials to make the embedded application independently authenticate.
Deliberate boundaries
Plain Vue does not provide:
- SSR execution or hydration payloads;
- Nitro request handling or
serverConvex(); - Better Auth setup or an auth proxy;
- route middleware;
- MCP server behavior; or
- application roles, permissions, workflows, or authorization.
Install @lupinum/better-convex-nuxt for the Nuxt-only features and
@lupinum/better-convex-mcp only for an MCP resource server.
The experimental MCP Apps client is isolated in
@lupinum/better-convex-vue/mcp-app.