Handle SSR manually with zero-vue
mainIf you are not using the Nuxt module, you must manually manage the Zero instance lifecycle to avoid leaking state or connections across requests.
1. Scope composables per request
Do not call createZeroComposables at the module level in an SSR environment. Instead, instantiate it within the request lifecycle (e.g., inside a Nuxt plugin or via useNuxtApp) and ensure the instance is closed after the response is rendered.
2. Use useAsyncData for SSR-rendered data
useQuery is reactive and relies on client-side synchronization, meaning it will return an empty array during the initial synchronous SSR render. To include data in the SSR payload, use zero.run(query, { type: 'complete' }) inside an useAsyncData call and then sync the result to the live view on the client.
3. WebSocket considerations
Zero attempts to connect via WebSocket if cacheURL is provided. In Node environments where you do not want the server to connect to zero-cache, leave cacheURL unset. If you do set it, ensure you call zero.close() after rendering.
// Example of manual SSR query implementation
export function useZeroSsrQuery(key, query) {
const zero = useZero()
const { data: liveRows, status } = useQuery(query)
const asyncData = useAsyncData(`zero:${key}`, async () => {
if (!zero.value.server) {
return []
}
return await zero.value.run(query, { type: 'complete' })
}, {
default: () => [],
transform: rows => JSON.parse(JSON.stringify(rows)),
})
if (import.meta.client) {
watch([liveRows, status], ([rows, status]) => {
if (status === 'complete') {
asyncData.data.value = rows
}
}, { immediate: true })
}
return asyncData
}