Header Logo

Streaming a Realtime AI Avatar on an In-Store Display with Next.js and WebSocket

Streaming a Realtime AI Avatar on an In-Store Display with Next.js and WebSocket

Build a Next.js kiosk for realtime AI avatar streaming with WebSocket/WebRTC, server-side sessions, and reconnect logic.

Introduction


Streaming a realtime AI avatar to an in-store display is mostly a systems integration problem: you need a low-latency conversation loop, a video surface that can stay synchronized with speech, and a display app that can reconnect cleanly when the network hiccups. The core challenge is not rendering video; it’s keeping audio, text generation, and avatar animation aligned well enough that the interaction feels live instead of “chatbot in a box.”


In this post, I’ll walk through a practical architecture for a Next.js kiosk or signage app that receives a live avatar feed over WebSocket/WebRTC-style streaming, renders it full screen, and stays resilient on a retail network. We’ll focus on the parts developers usually trip over: session creation, transport choice, reconnect behavior, autoplay restrictions, and how to keep secrets out of the browser.


Architecture: keep the display thin, keep the session server-side


For an in-store display, the frontend should do as little as possible. The browser should not hold API keys, should not synthesize prompts, and ideally should not orchestrate agent state. Instead:


  1. A backend endpoint creates a realtime avatar session.

  2. The kiosk app receives a short-lived session payload or embed URL.

  3. The browser connects to the streaming channel and renders the avatar.

  4. The same browser keeps the connection alive, retries on failure, and restarts cleanly if the session expires.


This separation matters because retail displays are operationally hostile: Wi-Fi drops, browsers get restarted, power cycles happen, and devices may sit behind restrictive firewalls. If the browser needs long-lived credentials, you eventually leak them. If the browser is only handed a session-scoped connection artifact, you can rotate API keys and enforce server-side policy without redeploying the kiosk.


What “realtime avatar streaming” actually means


Under the hood, a talking avatar pipeline usually has three time-sensitive streams:


  • Input: text or audio from the user, or both.

  • Agent output: generated text, speech, or speech plan.

  • Video face: the avatar animation and lip sync that tracks the speech timeline.


If you are streaming an avatar to a display, you care about two latency budgets:


  • Conversation latency — time from user utterance to agent response beginning.

  • AV sync latency — time from the spoken audio to the matching mouth motion and facial expression.


For a public-facing screen, the second one is often more noticeable than the first. A response that starts 400 ms late can still feel natural; a response where the mouth moves 300 ms ahead of the audio feels broken immediately.


That’s why the client should treat the avatar as a synchronized media stream, not as a series of independently updated images. Even if your transport layer uses WebSocket for control messages, the actual media path should preserve timing information and support jitter buffering. In practice, the UI should render a stable video element or canvas surface and let the streaming layer manage frame cadence.


Next.js kiosk app: the client responsibilities


A Next.js app for an in-store display usually has three jobs:


  • Fetch a short-lived session artifact from your backend.

  • Open the streaming connection and attach the avatar view.

  • Monitor state and recover from disconnects.


The important thing is that the display app never talks to your avatar provider with an API key directly. Instead, your backend handles auth and returns only what the browser needs to join the session.


export default function KioskPage() {
}
export default function KioskPage() {
}
export default function KioskPage() {
}


The real connection code will depend on the streaming SDK or embed mechanism you choose, but the lifecycle should look like this:


  1. On mount, request session info from your own backend.

  2. Initialize the avatar stream.

  3. Subscribe to connection state changes.

  4. On disconnect, retry with backoff and surface a visible fallback if recovery fails.


For kiosks, you should also handle browser autoplay policy. If the avatar includes audio, some browsers require a user gesture before unmuted playback. The easiest operational fix is to start muted and switch to audible playback after a tap or a trusted kiosk initialization flow.


Server-side session creation and secret handling


Session creation belongs on the server because it usually requires a long-lived API key and may include tenant-specific policy like allowed prompts, rate limits, or time windows. A minimal backend route can call your avatar API, stash the resulting session metadata, and hand the browser only the short-lived connection details.


import os

session = resp.json()
import os

session = resp.json()
import os

session = resp.json()


The exact request body and returned fields are documented, but the shape above is enough to show the pattern: keep the authenticated call on the server, then pass only the session-scoped information to the browser. If you have multiple locations, tag sessions with store IDs and correlate them with your monitoring so you can tell whether a bad experience is local networking or a platform issue.


A practical production detail: do not create a new session on every page refresh unless the backend is cheap and idempotent. Cache or resume when the screen reconnects within a short window, or your operations team will spend time debugging session churn that is really just browser reload behavior.


Rendering and operational gotchas on an in-store display


Retail hardware is not a clean test environment. A few issues come up repeatedly:


  • Resolution mismatch: if the display is 1080p but your avatar surface is rendered at a smaller CSS size and upscaled, the face will look soft. Set the canvas/video container to the display’s native aspect ratio and size.

  • Network transitions: captive portals, flaky Wi-Fi, and firewall timeouts can drop long-lived streams. Implement reconnect with exponential backoff and distinguish “transient” from “expired session.”

  • Browser restarts: Chrome kiosk mode will still crash or be killed by the OS. On startup, the app should be able to recover without operator intervention.

  • Idle behavior: if the avatar is waiting for input, define a visible idle loop or prompt rotation so the screen does not look frozen.


Also be careful with audio routing. In-store displays often have external speakers, TV audio processing, or HDMI audio switching. If you are syncing lip movement to spoken output, test the final device chain, not just the browser on a laptop. Audio buffering introduced by the OS or display hardware can make the avatar appear slightly ahead of the voice even if your stream is correct.


Where Protoface fits


This is exactly the sort of problem Protoface is meant to simplify: you keep your app focused on display logic, while the platform handles the avatar/session side. For a browser-based kiosk, the useful surface is the realtime session API plus the customer-managed iframe embed model. The former is what your backend uses to create and govern sessions; the latter is the simplest way to get a synchronized avatar into a browser without exposing credentials client-side.


If you want to wire this up directly, the docs at docs.protoface.com are the right starting point. A backend can create a session with the REST API, then the Next.js page can load the session in a controlled way. If your deployment only needs the avatar visible on a webpage and you do not want to build session plumbing yourself, the iframe path is especially relevant because it avoids shipping API keys to the browser entirely.


curl -X POST https://api.protoface.com/sessions \
}'
curl -X POST https://api.protoface.com/sessions \
}'
curl -X POST https://api.protoface.com/sessions \
}'


The exact field names may differ depending on how you model avatars and sessions, so treat this as illustrative and verify against the docs before wiring it into production. The important part is the division of responsibility: server creates the session, browser renders it, and the transport stays short-lived and recoverable.


Production checklist


Before you roll the display into a store, test these conditions explicitly:


  • Cold boot from a powered-off state.

  • Wi-Fi disconnect and reconnect without re-deploying.

  • Browser reload while a session is active.

  • Muted autoplay and subsequent unmute flow if audio is enabled.

  • Display sleep/wake cycle.


If the avatar is part of a broader voice-agent workflow, you can also validate the agent on the backend independently of the browser. That lets you confirm that latency is coming from the network/display path rather than the model or speech pipeline.


Conclusion


The main design rule for an in-store realtime avatar is simple: keep the browser thin, keep secrets server-side, and treat the avatar as a streaming session that can fail and recover. Once you do that, Next.js is a perfectly reasonable kiosk shell for a synchronized talking face on a retail display.


From there, the implementation path is straightforward: create sessions on your backend, stream the avatar to the client, and harden reconnect behavior against the realities of store networks. For API details, session formats, and integration examples, start with the docs and adapt the flow to your display environment.

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.