Why Better Convex Nuxt
Understand which integration problems the module solves and which choices remain yours.
Convex already provides typed functions, consistent data, and live subscriptions. Nuxt adds a server-rendering and application lifecycle that a browser-only Convex client does not coordinate by itself.
Better Convex Nuxt owns that integration boundary.
The problem is the transition
A production Nuxt query may need to:
- Run with the current request identity during SSR.
- Serialize its result safely into the Nuxt payload.
- Hydrate without replacing rendered content with a loading state.
- Start a client subscription under the correct identity.
- React when arguments change.
- Clear identity-owned state before another user can observe it.
- Stop its resources when the owning scope ends.
Those steps are coupled. Implementing each one independently creates races and duplicate sources of truth.
With the module, the page describes the query it needs:
const { data, status, error } = await useConvexQuery(api.projects.list, {})The runtime coordinates the transition.
The deliberate choices
Start with SSR and continue with live data
Queries use SSR and subscriptions by default. This fits primary page content that should appear in the initial HTML and remain current.
It also means one HTTP query runs during SSR before the browser subscription starts. Disable the stage the page does not need:
const result = await useConvexQuery(
api.metrics.snapshot,
{},
{
server: false,
subscribe: false,
},
)Treat identity changes as data boundaries
Signing in, signing out, or switching users changes which query results are safe to display. The runtime replaces the identity-scoped client and clears identity-owned query state. Same-user token refresh keeps the identity generation.
This is why useConvex() returns a stable four-method handle instead of the raw client.
Let each system keep its source of truth
Convex owns server-backed query data. Better Auth owns the session. Nuxt owns the payload. Your application owns authorization.
The module coordinates these owners without introducing another entity store or permission model.
Prefer supported public boundaries
The package exposes explicit client, error, auth-client, and server entry points. Application code does not need deep imports or Convex private modules.
What you gain
- Less lifecycle code in pages and plugins
- One documented query model for SSR and live updates
- Defined loading, stale, error, and connection state
- Identity-safe behavior across authentication transitions
- A typed Better Auth client definition instead of a second client instance
- One error shape across client and server calls
- Direct Nitro integration when a server boundary is actually needed
What you still decide
- Which Convex functions a user may call
- Whether a page needs SSR, a live subscription, both, or neither
- Which Better Auth plugins belong in the product
- Whether user data belongs in the session, Better Auth component, JWT, or an application projection
- How the UI presents errors, offline state, and pending operations
That boundary is intentional. The module removes integration work without taking ownership of product policy.