SSR, Hydration, and Real-Time
Choose whether a live query begins during SSR or only in the browser.
SSR and live updates solve different stages of one page lifecycle.
- SSR produces useful initial HTML.
- Hydration reuses that server result in Vue.
- Browser observation keeps the result current after the page becomes interactive.
Choose where execution begins
| Requirement | Options | Cost |
|---|---|---|
| Initial HTML and live updates | Default | SSR HTTP query plus browser observation |
| Browser-only live data | server: false | Empty or pending server output, then browser execution |
const livePage = useConvexQuery(api.posts.list)
const privateBrowserData = useConvexQuery(
api.notifications.list,
{},
{ server: false, auth: 'required' },
)Why two Convex executions appear
For the default lifecycle, the Convex dashboard may show an HTTP execution from SSR and a WebSocket execution in the browser. The transports have different jobs. Convex can reuse query computation internally, but the module does not describe the two stages as one billable call.
Immediate state and optional await
useConvexQuery returns refs immediately. Its Nuxt return is also a native Promise when navigation or suspense should wait for initial settlement:
const posts = useConvexQuery(api.posts.list)
const settled = await postsThe Promise resolves for a query error, skip, and server: false as well as success. The awaited value is not itself Promise-like; read its refs to determine the outcome.
Hydration is reuse, not a second store
Nuxt owns the payload that crosses the server/browser boundary. Convex owns live client query state. The composable connects them and exposes one Vue view.
Do not add a Pinia copy solely to carry the SSR result. That produces another state owner without improving the lifecycle.
Private data and SSR
SSR can render authenticated data when the request session is available. That is useful for a dashboard and inappropriate for some highly private or user-agent-specific data.
server: false controls rendering location. It is not authorization. The Convex function still enforces access.
Rendering browser-only data
For server: false, render a server-compatible pending state:
<template>
<NotificationSkeleton v-if="status === 'pending'" />
<p v-else-if="error">Notifications are unavailable.</p>
<NotificationList v-else-if="data !== undefined" :items="data" />
</template>Avoid branching on browser-only globals during the initial render. That causes a hydration mismatch before the query lifecycle can help.