Build an AI Voice Agent with Nuxt 3 + Vue 3.5 + OpenAI Realtime (2026)
Nuxt 3 Nitro server routes mint ephemeral OpenAI keys, Vue 3.5 composables wrap WebRTC, and Pinia holds the call state. Sub-700ms voice agent in 200 lines.
TL;DR — Nuxt 3.13+ on Vue 3.5 ships a built-in Nitro server, perfect for hiding OpenAI keys. Wrap WebRTC + the Realtime API in a
useVoiceAgentcomposable for a clean Vue voice UI.
What you'll build
A Nuxt 3 page with a Talk button that uses an ephemeral key minted by a Nitro server route, opens WebRTC to OpenAI gpt-realtime, and streams transcripts into a Pinia store.
Prerequisites
nuxt@^3.13,vue@^3.5,pinia@^2.2.OPENAI_API_KEYin.env.- Node 20+ or Bun 1.3.
Architecture
flowchart LR
V[Nuxt page] --> N[Nitro /api/realtime/key]
N -- POST sessions --> OA1[OpenAI]
OA1 --> N --> V
V -- WebRTC SDP --> OA2[OpenAI Realtime]
Step 1 — Nitro endpoint
```ts
// server/api/realtime/key.post.ts
export default defineEventHandler(async () => {
const r = await $fetch<{ client_secret: { value: string } }>(
"https://api.openai.com/v1/realtime/sessions",
{
method: "POST",
headers: { Authorization: Bearer ${process.env.OPENAI_API_KEY} },
body: { model: "gpt-realtime", voice: "alloy" },
},
);
return r;
});
```
Step 2 — Composable
```ts // composables/useVoiceAgent.ts export function useVoiceAgent() { const live = ref(false); const transcript = ref(""); const audioEl = ref<HTMLAudioElement | null>(null);
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
async function start() { const { client_secret } = await $fetch("/api/realtime/key", { method: "POST" }); const pc = new RTCPeerConnection(); pc.ontrack = (e) => audioEl.value && (audioEl.value.srcObject = e.streams[0]); const ms = await navigator.mediaDevices.getUserMedia({ audio: true }); ms.getTracks().forEach((t) => pc.addTrack(t, ms));
const dc = pc.createDataChannel("oai-events");
dc.addEventListener("message", (e) => {
const evt = JSON.parse(e.data);
if (evt.type === "response.audio_transcript.delta")
transcript.value += evt.delta;
});
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const ans = await fetch(
"https://api.openai.com/v1/realtime?model=gpt-realtime",
{ method: "POST", body: offer.sdp,
headers: { Authorization: `Bearer ${client_secret.value}`,
"Content-Type": "application/sdp" } });
await pc.setRemoteDescription({ type: "answer", sdp: await ans.text() });
live.value = true;
} return { live, transcript, audioEl, start }; } ```
Step 3 — Page
```vue
{{ transcript }}
\`\`\`
Step 4 — Pinia store for multi-page state
```ts // stores/calls.ts export const useCalls = defineStore("calls", () => ({ state: () => ({ history: [] as { role: string; text: string }[] }), })); ```
Step 5 — Deploy
npx nuxi build && nuxt preview locally, or use the nitro-cloudflare preset for Cloudflare Pages. Vercel and Netlify presets ship out of the box.
Step 6 — Tool calls
Listen for response.function_call_arguments.done, run a Nitro endpoint to execute the tool server-side (so you keep secrets server-only), and reply via the data channel.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
Pitfalls
process.envin Nitro vsuseRuntimeConfig— use the latter for typed config.- WebRTC + SSR: The voice page must be
<ClientOnly>or setssr: falseindefinePageMeta. - Vapor mode (preview): Vue 3.5's experimental Vapor mode skips VDOM but is still preview — opt-in carefully.
How CallSphere does this in production
CallSphere's stack is multi-framework: Healthcare (FastAPI), OneRoof (Next.js 16 + React 19), Salon (NestJS 10 + Prisma), Sales (Node.js 20 + React 18 + Vite). Some agency white-label customers prefer Vue/Nuxt — supported via the same realtime relay. 37 agents · 90+ tools · 115+ DB tables · 6 verticals. $149/$499/$1,499, 14-day trial, 22% affiliate.
FAQ
Vue 3.5 vs 3.4? 3.5 brings reactivity perf wins and useTemplateRef.
WebRTC vs WebSocket on Nuxt? WebRTC for browser-direct, WebSocket if you need server-side audit/policy.
Cloudflare Pages limit? WS connections capped at 100 concurrent on free tier — bump to Workers Paid for production.
Ephemeral key TTL? 60s default; refresh before each call.
Sources
- Nuxt 3 docs - https://nuxt.com/
- Mamezou - Nuxt + OpenAI Realtime - https://developer.mamezou-tech.com/en/blogs/2024/10/16/openai-realtime-api-nuxt/
- Vue.js AI SDK Getting Started - https://ai-sdk.dev/docs/getting-started/nuxt
- VueSchool - AI Interfaces with Vue + Nuxt - https://vueschool.io/courses/ai-interfaces-with-vue-nuxt-and-the-ai-sdk
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.