Skip to main content
Version: v1

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.

app/layout.tsx
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

<VoiceBotWidget publicKey="pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" variant="voice" />

variant="voice" (the default) loads widget-voice.js.

Props

PropTypeDefaultDescription
publicKeystring (required)Your pk_ publishable key.
variant'voice' | 'chat''voice'Which widget bundle to load.
langstringForce the widget UI language (e.g. 'uk', 'en'). Omit to let the backend pick.
scriptSrcstringhttps://api.monoverse.tech/widgetOverride the CDN base (self-hosted API).
onError(error: Error) => voidCalled on script load failure or an empty key.
onNavigate(target: VoiceBotNavTarget) => voidCalled on any bot navigation. Route it through your SPA router so the live session survives.
onActionVoiceBotActionHandlersCart / variant / filter handlers — the acting tier. See Capabilities & host actions.
capabilitiesVoiceBotCapability[]Narrow the server-granted capability set for the current page (narrow-only; never widens it).
toolHandlersVoiceBotToolHandlersLow-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's useEffect injector 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:

VoiceBotEmbed.tsx
'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.