Header Logo

Adding User-Selectable Avatar Presets in a Next.js App with the OpenAI Realtime API

Adding User-Selectable Avatar Presets in a Next.js App with the OpenAI Realtime API

Next.js avatar presets with OpenAI Realtime: server-side session creation, preset persistence, and secure reconnects.

Introduction


Adding user-selectable avatar presets sounds trivial until you wire it into a realtime avatar pipeline. The UI problem is simple: let a user pick from a small set of faces, then remember that choice. The systems problem is harder: in a streaming conversation, the selected preset has to flow into session creation, stay consistent across reconnects, and not leak any credentials into the browser.


This post shows one practical way to do that in a Next.js app using the OpenAI Realtime API for the conversation layer and an avatar service for the video face. By the end, you should be able to:


  • model avatar presets cleanly in your app

  • create a realtime session from the server, not the browser

  • start a streaming conversation with a user-selected avatar

  • avoid the common auth and state-management mistakes that break these flows


What “avatar preset” should mean in a realtime app


An avatar preset should be an immutable product choice, not a free-form blob of UI state. In practice, it’s usually a small object with a stable identifier and the fields needed to render and configure the avatar session.


For example:


{
}
{
}
{
}


That structure matters because the same preset needs to be used in at least three places:


  1. the selection UI

  2. your server-side session bootstrap

  3. the persisted conversation state, so reconnects restore the same face


The important design constraint is that the browser should only ever send a preset ID. The server resolves that ID to the real avatar configuration and any upstream session parameters. If you pass raw avatar IDs or provider secrets into client code, you’ll eventually ship a credential leak.


Keep the browser dumb: let Next.js own session creation


The clean pattern in Next.js is to let the client choose a preset, then call a server route that creates the realtime session and returns only the data the browser needs to connect. That route can also validate that the preset is allowed for the current user or tenant.


In a typical app, the flow looks like this:


  1. User selects a preset in the React UI.

  2. Client posts presetId to /api/realtime/session.

  3. Server maps presetId to the underlying avatar config.

  4. Server creates the session with your upstream realtime provider and avatar service.

  5. Server returns a short-lived token or session descriptor to the browser.


That architecture gives you a few things for free:


  • API keys stay server-side.

  • You can enforce tenant-specific preset availability.

  • You can swap avatar implementations without changing the UI.

  • You can audit and rate-limit session creation centrally.


Client-side selection in Next.js


The UI itself can be plain React. Keep the preset list local to the app or fetch it from your own backend. The key is that the selected preset becomes an input to session creation, not the session itself.


'use client';

}
'use client';

}
'use client';

}


Two details are worth calling out:


  • The browser never needs to know your avatar provider API key.

  • The client should not decide the avatar’s real backend identity; it should only choose a preset.


Server route: resolve the preset and create the session


Your server route is where preset IDs become real session configuration. If you are using OpenAI Realtime for the conversation and an avatar service for the face, this is where you bind them together.


A minimal example in a Next.js route handler might look like this:


import { NextResponse } from 'next/server';

}
import { NextResponse } from 'next/server';

}
import { NextResponse } from 'next/server';

}


In production, this route should also:


  • authenticate the user

  • verify that the preset is allowed for that account

  • log the preset ID for usage analytics

  • apply expiry limits to the returned session token


If you need to create or manage avatar resources programmatically, use the server-side API rather than a browser call. The REST API is authenticated with API keys and is intended for this kind of backend orchestration. For the exact request shapes, refer to the docs.


How the streaming pieces fit together


Realtime avatars are not “video files with chat.” They are streaming endpoints that synthesize a talking face from the current conversational state, usually over WebRTC or a similar low-latency transport. The avatar needs text or speech input quickly enough to stay synchronized with the model’s response. That means latency is dominated by the chain of events, not any single API call:


  1. user audio is captured

  2. speech is transcribed or directly routed into the conversation model

  3. the model produces a response incrementally

  4. the avatar stream renders mouth movement and facial motion from that response


When you add selectable presets, you are really choosing a bundle of parameters that affects the entire chain: which avatar to render, which voice to use, and what instructions shape the model’s behavior. If those parameters diverge between the client and the avatar session, you get an obvious mismatch: the UI says “friendly support,” but the session sounds like a product demo.


A few practical gotchas:


  • Don’t recreate sessions on every render. Treat the selected preset as stable state until the user changes it.

  • Keep reconnect logic idempotent. Rejoining should reuse the same preset ID and restore the same server-side session or create an equivalent one.

  • Use short-lived tokens if your browser connects directly to a realtime transport.

  • Separate preset metadata from generated session state. They are not the same thing.


Using Protoface for the avatar side of the stack


This is a good fit for Protoface because the avatar/session boundary is already exposed as a backend service. In this architecture, your Next.js app stays responsible for preset selection and auth, while the server creates the avatar session and hands the browser only a short-lived connection payload.


That maps well to the REST API and the Python SDK if you want to automate avatar creation or session management on the backend. For example, a backend service can create a session from a preset and return a browser-safe descriptor:


from protoface import ProtofaceClient

print(session.id)
from protoface import ProtofaceClient

print(session.id)
from protoface import ProtofaceClient

print(session.id)


If you are using a voice agent stack, the LiveKit integration also makes this pattern straightforward: drop the avatar into the agent so the voice output and facial animation stay synchronized. The plugin is published as livekit-plugins-protoface on PyPI, and the examples in the repo are the fastest way to see how the handoff works in practice.


Testing, persistence, and production concerns


Once the basic flow works, the last mile is mostly about state and failure modes.


Persist the selected preset on the user profile if the choice should survive reloads. If the preset is session-scoped, keep it in route state or a conversation record instead. Either way, the server should treat the preset ID as the source of truth when generating the session.


Also verify these cases before shipping:


  • invalid preset IDs return a 400, not a default avatar

  • unauthorized users cannot select premium presets

  • session reconnects preserve the original preset

  • rate limits prevent one user from creating many avatar sessions in a loop


If you expose the avatar surface to untrusted clients, prefer an iframe-based embed model for isolation. If you are building a first-party app with your own auth and app shell, the Next.js route-handler approach is usually simpler and gives you more control.


Conclusion


User-selectable avatar presets are mostly a session-management problem disguised as a UI feature. Keep the preset list small and explicit, let the browser send only a preset ID, resolve that ID on the server, and create the realtime session there. That keeps credentials off the client, makes reconnects predictable, and gives you a single place to enforce product rules.


If you want to implement this with Protoface, start by wiring your Next.js backend to create avatar sessions from preset IDs, then connect the browser to the returned realtime session data. The public docs at docs.protoface.com cover the API details, and the GitHub quickstarts are a good way to compare integration styles before you commit to one.

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.