Header Logo

How to Build a Realtime AI Support Avatar in Next.js with WebSocket Streaming

How to Build a Realtime AI Support Avatar in Next.js with WebSocket Streaming

Build a realtime AI support avatar in Next.js with WebSocket streaming, session minting, barge-in, and synced avatar playback.

Introduction


Adding a realtime AI support avatar to a Next.js app is mostly an integration problem: you need low-latency audio, a streaming transport, a stateful agent loop, and a video face that stays in sync with the agent’s speech. The hard part is not rendering a talking head; it’s keeping the voice, avatar timing, and conversation state aligned under network jitter and browser constraints.


This post shows a practical architecture for a support widget in Next.js that streams microphone audio to a voice agent over WebSocket/WebRTC-style realtime transport, receives streamed responses, and displays a synchronized avatar in the UI. By the end, you should understand the client/server split, the realtime message flow, and where an avatar service fits without leaking credentials into the browser.


What a realtime support avatar actually needs


A support avatar is a thin presentation layer over a conversational agent. The user speaks or types, the browser streams audio or events to your backend, the agent produces partial and final responses, and the avatar renders as the agent speaks. For the experience to feel live, you need three things to happen concurrently:


  • Low-latency input streaming so the agent starts processing before the user finishes speaking.

  • Incremental output so text, audio, and lip motion can begin before the full response is complete.

  • Deterministic session state so interruptions, barge-in, and turn-taking don’t desynchronize the UI.


In practice, Next.js is best used for the web app shell, auth, and session initiation. The realtime loop should be isolated to a client component or a dedicated service boundary so you don’t accidentally route media through serverless request/response handlers. If you are using WebSocket streaming directly, the browser can maintain a long-lived socket to your backend; if you are using a WebRTC-based agent stack, the browser can join a live session and exchange audio/video/media tracks with your agent runtime.


Recommended architecture in Next.js


A straightforward production shape looks like this:


  1. The user opens a support page in Next.js.

  2. Your app creates or fetches a conversation/session record from your backend.

  3. The browser connects to the realtime layer and starts sending microphone audio frames.

  4. The agent backend performs transcription, reasoning, and response synthesis.

  5. The avatar renders the agent’s voice as synchronized video in the page.


The important boundary is that the browser should never contain long-lived secrets. It can hold an ephemeral session token or join URL, but not API keys. That keeps your support widget safe to embed and simpler to operate.


Client streaming: keep the UI responsive and the media pipeline separate


In a Next.js app, use a client component for the realtime session and a normal server route for minting any ephemeral credentials. The client component can manage microphone capture, socket lifecycle, transcript state, and avatar playback state. Avoid folding this into a single React component that also does layout work; when the audio pipeline hiccups, you want the UI to stay predictable.


For a WebSocket-based integration, the browser typically streams small PCM or encoded audio chunks, plus control messages for events like start, stop, interrupt, or text input. Your backend should treat the socket as a bidirectional event channel, not as a classic request/response API. That means you need explicit session IDs, turn IDs, and a small state machine for:


  • idle

  • listening

  • processing

  • speaking

  • interrupted


That state machine matters because users interrupt bots constantly. If the user starts talking while the avatar is speaking, you need a barge-in path that stops audio playback, cancels any ongoing synthesis, and lets the new utterance take precedence.


Server-side session minting in Next.js


Keep backend credentials on the server and exchange them for short-lived session data. A minimal route handler could create a conversation record, initialize whatever agent backend you use, and return only what the browser needs to join the realtime session.


export async function POST() {

}
export async function POST() {

}
export async function POST() {

}


The exact fields depend on your agent stack, but the pattern is the same: authenticate the user on your origin, issue a short-lived join credential, and keep the transport details out of the page bundle.


Avatar timing: why lip sync is a transport problem, not just a rendering problem


Realtime avatars feel convincing when video mouth motion is aligned with the speech audio timeline, not merely with the text stream. If your agent streams audio in chunks, the avatar renderer needs enough timing metadata to stay in lockstep with that audio. If the avatar backend gets the generated speech before the browser does, you can still get good sync as long as the playback pipeline preserves timestamps and the UI doesn’t buffer arbitrarily.


This is where developers often get tripped up:


  • Text-only streaming is not enough for visual sync. A face cannot lip-sync from final text alone.

  • Audio buffering adds latency but smooths glitches. Too much of it makes the agent feel sluggish.

  • Independent animation timers drift over time. The avatar must follow the audio clock, not an approximate UI clock.


For support use cases, aim for a small, consistent buffer rather than the lowest possible delay. Users notice stalls and desynchronization more than they notice a few hundred milliseconds of latency.


Example: connecting a voice agent with a video face


If your voice agent already runs in LiveKit, the simplest way to add a synchronized talking face is to use the LiveKit plugin. That lets the agent keep its existing speech pipeline while gaining a realtime video avatar as part of the same session. The integration surface is intentionally small: install the plugin, enable it in the agent, and pass through the avatar/session configuration your backend creates.


from livekit.agents import JobContext

await agent.start(ctx)
from livekit.agents import JobContext

await agent.start(ctx)
from livekit.agents import JobContext

await agent.start(ctx)


If you are not on LiveKit, you can still use the same general design: your agent runtime streams speech, the avatar service consumes the generated audio, and the browser receives a synchronized visual stream. The key implementation detail is to keep the agent and avatar tied to the same realtime session so interruptions, turn-taking, and voice changes stay coherent.


One concrete Protoface integration path


Protoface is useful here because it gives you a developer-facing avatar layer without forcing you to expose long-lived secrets in the browser. For a Next.js support widget, that usually means your server creates or manages the avatar/session, then your client joins with an ephemeral value or uses an iframe embed when you want a fully isolated frontend path. The REST API is the right surface when your app needs to provision avatars or sessions programmatically; the Python SDK is handy for backend jobs and admin workflows.


A minimal API call looks like this:


curl -X POST https://api.protoface.com/v1/sessions \
-d '{"avatar_id":"avt_123","metadata":{"channel":"support"}}'
curl -X POST https://api.protoface.com/v1/sessions \
-d '{"avatar_id":"avt_123","metadata":{"channel":"support"}}'
curl -X POST https://api.protoface.com/v1/sessions \
-d '{"avatar_id":"avt_123","metadata":{"channel":"support"}}'


Use the exact request shape from the docs; the point is that session creation stays server-side, and the browser only receives what it needs to join the session. For implementation details, consult the docs and the relevant quickstart or SDK repository when you wire it into your agent stack.


Practical trade-offs and gotchas


Three issues show up repeatedly in real deployments:


  • Mic permission and autoplay policy: browsers often require a user gesture before audio playback starts. Design your “Start support call” button to initialize both capture and playback.

  • Session recovery: if the socket drops, decide whether to reconnect into the same turn or force a clean restart. Support experiences usually favor fast reconnection with explicit session IDs.

  • Fallback modes: if video fails, keep the text and audio agent usable. An avatar should enhance the experience, not become a hard dependency for support.


Also be careful with transcript persistence. Store the conversation state on the server, not just in React state, so you can resume, audit, or hand off to a human agent later. The avatar is just one consumer of that state.


Conclusion


The clean way to build a realtime AI support avatar in Next.js is to treat it as a streaming system: the browser captures input, a backend agent processes turns, and the avatar renders speech with tight timing guarantees. Keep credentials server-side, use a persistent realtime transport, and make interruption handling part of the design from day one.


If you want to implement this with less glue code, start with the docs at docs.protoface.com and the relevant quickstart or integration repository for your stack. Then wire one narrow path end to end: session minting, streaming audio, avatar playback, and barge-in. Once that works reliably, everything else is just product polish.

Add a face to your AI.

No credit card needed.

Add a face to your AI.

No credit card needed.

Add a face to your AI.

No credit card needed.