Installation
Install
npm install @monoverse/voicebot-next
next (^14 or ^15), react, and react-dom are peer dependencies.
Render in the root layout (App Router)
Render <VoiceBotWidget> once in your root layout so it persists across client navigations
and never re-injects per route. The component is 'use client'; importing it into a Server Component
layout is fine.
import { VoiceBotWidget } from '@monoverse/voicebot-next';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="uk">
<body>
{children}
<VoiceBotWidget publicKey="pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" />
</body>
</html>
);
}
It renders nothing visible — the widget UI is mounted by the deployed bundle it loads into its own
Shadow DOM host. The widget core is a one-per-page singleton; next/script caches the tag by
src, so a re-render never opens a second WebSocket or microphone.
Voice or chat
- Voice
- Chat
<VoiceBotWidget publicKey="pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" variant="voice" />
variant="voice" (the default) loads widget-voice.js.
<VoiceBotWidget publicKey="pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" variant="chat" />
variant="chat" loads widget-chat.js.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
publicKey | string (required) | — | Your pk_ publishable key. |
variant | 'voice' | 'chat' | 'voice' | Which widget bundle to load. |
lang | string | — | Force the widget UI language (e.g. 'uk', 'en'). Omit to let the backend pick. |
scriptSrc | string | https://api.monoverse.tech/widget | Override the CDN base (self-hosted API). |
onError | (error: Error) => void | — | Called on script load failure or an empty key. |
onNavigate | (target: VoiceBotNavTarget) => void | — | Called on any bot navigation. Route it through your SPA router so the live session survives. |
onAction | VoiceBotActionHandlers | — | Cart / variant / filter handlers — the acting tier. See Capabilities & host actions. |
capabilities | VoiceBotCapability[] | — | Narrow the server-granted capability set for the current page (narrow-only; never widens it). |
toolHandlers | VoiceBotToolHandlers | — | Low-level override for canonical action names. Most apps use onAction instead. |
The last three (onAction, capabilities, toolHandlers) and onNavigate are the acting tier —
they let the bot add to cart, pick a variant, filter, and navigate. See
Capabilities & host actions and
Custom site integration.
SSR safety
next/script with strategy="afterInteractive" loads only in the browser after hydration, so
the widget code (which touches window/document) never runs on the server — the component is
SSR-safe by construction. On an empty publicKey it renders nothing and calls onError.
Teardown caveat (why root-layout placement matters)
next/script caches the <script> by src and intentionally does not remove it on unmount.
When the component unmounts it tears down the widget instance (window.VoiceBot?.destroy?.() —
closing the WebSocket and releasing the mic), but the cached tag stays. Because the script is cached,
re-mounting on a later route may not re-run the bundle.
- Place the widget once in the root layout (it never unmounts) — this is the recommended path.
- To switch the key at runtime, remount the host with a React
key=. - If you must mount it on specific routes with full re-init, use
@monoverse/voicebot-react'suseEffectinjector instead, which removes the tag on cleanup.
Bare embed (no package)
If you'd rather not add a dependency, the underlying mechanism is a single <Script> — this is what
the wrapper writes for you:
'use client';
import Script from 'next/script';
interface VoiceBotEmbedProps {
publicKey: string;
apiBase?: string;
variant?: 'voice' | 'chat';
}
export function VoiceBotEmbed({
publicKey,
apiBase = 'https://api.monoverse.tech',
variant = 'voice',
}: VoiceBotEmbedProps) {
return (
<Script
src={`${apiBase}/widget/widget-${variant}.js`}
strategy="afterInteractive"
data-public-key={publicKey}
data-api-base={apiBase}
/>
);
}
The full reference lives in examples/embed/next/.
The package source, props table, and changelog live in the package README on npm.