Skip to Content
QuickstartsVue & Nuxt

Vue & Nuxt

Build and query a ZXCV database from a Nuxt app, then ship it to the edge. You’ll end with a page that reads live data with useAsyncData. A short note at the end covers plain Vue + Vite.

AI Assistance
Help me connect ZXCV to my Nuxt app. Do the following:
1. Install the @zxcv.build/sdk package.
2. Initialize a client with my project ref + anon key (from Settings → API), using Nuxt's env convention (NUXT_PUBLIC_ env vars mapped onto runtimeConfig.public), exposed via a Nuxt plugin.
3. Fetch rows from a "todos" table with useAsyncData and render them idiomatically.
4. Keep the anon key on the client — authorization is enforced server-side by my policies.
Ask me for my project ref and anon key if I haven't given them.

The anon key is public and safe to ship, because access is enforced server-side by your authorization rules.

Create a project

In the dashboard  click Start building, or run zxcv from your terminal. Open Settings → API and copy your project ref and anon key.

Create a table

In the SQL editor, create a todos table with a couple of rows:

create table todos ( id bigint generated always as identity primary key, title text not null, is_complete boolean not null default false ); insert into todos (title) values ('Read the docs'), ('Ship an app');

Install the SDK

bash npm install @zxcv.build/sdk

Initialize the client

Add your keys to .env. Nuxt exposes runtimeConfig values to the browser only when they live under public, and it auto-maps NUXT_PUBLIC_ env vars onto them:

.env
NUXT_PUBLIC_ZXCV_PROJECT=acme-store NUXT_PUBLIC_ZXCV_ANON_KEY=your-anon-key

Declare the matching runtimeConfig.public keys:

nuxt.config.ts
export default defineNuxtConfig({ runtimeConfig: { public: { zxcvProject: '', zxcvAnonKey: '', }, }, })

Expose a single shared client with a Nuxt plugin, so every component and load uses the same instance:

plugins/zxcv.ts
import { createClient } from '@zxcv.build/sdk' export default defineNuxtPlugin(() => { const config = useRuntimeConfig() const zxcv = createClient({ project: config.public.zxcvProject, anonKey: config.public.zxcvAnonKey, }) return { provide: { zxcv } } })

Fetch and render

Fetch with useAsyncData so the query runs on the server for the first render and hydrates on the client:

pages/index.vue
<script setup lang="ts"> const { $zxcv } = useNuxtApp() const { data: todos } = await useAsyncData('todos', async () => { const { data, error } = await $zxcv .from('todos') .select('id, title, is_complete') .order('id') if (error) throw error return data }) </script> <template> <ul> <li v-for="todo in todos" :key="todo.id">{{ todo.title }}</li> </ul> </template>

Want live updates? Subscribe with Realtime inside onMounted (client only) — it’s the same client, and the feed is filtered by the same authorization rules as select.

Deploy

Ship to the global edge from your terminal:

npm i -g zxcv zxcv link acme-store zxcv deploy --prod

Every push gets a preview URL; --prod promotes to your domain. See Deploy for custom domains and canaries.

Plain Vue + Vite

Without Nuxt there’s no server, so fetch from the browser. Vite exposes variables prefixed with VITE_:

.env
VITE_ZXCV_PROJECT=acme-store VITE_ZXCV_ANON_KEY=your-anon-key
src/zxcv.ts
import { createClient } from '@zxcv.build/sdk' export const zxcv = createClient({ project: import.meta.env.VITE_ZXCV_PROJECT, anonKey: import.meta.env.VITE_ZXCV_ANON_KEY, })
src/Todos.vue
<script setup lang="ts"> import { onMounted, ref } from 'vue' import { zxcv } from './zxcv' type Todo = { id: number; title: string; is_complete: boolean } const todos = ref<Todo[]>([]) onMounted(async () => { const { data, error } = await zxcv .from('todos') .select('id, title, is_complete') .order('id') if (error) console.error(error) else todos.value = data ?? [] }) </script> <template> <ul> <li v-for="todo in todos" :key="todo.id">{{ todo.title }}</li> </ul> </template>

Next steps