How to add an AI chatbot to a React or Next.js app
Why a script tag and not an npm package
The AskVault widget is plain JavaScript that builds its own interface inside a closed shadow DOM. Your styles cannot leak into it, its styles cannot leak out, and it never mounts anything into your React tree. That also means there is no peer dependency to keep in step with your React or Next.js version (it runs the same on React 16 through 19), and nothing extra in your JavaScript bundle; the browser fetches the widget separately and in parallel.
Option 1: index.html (Vite, Create React App)
Paste the tag just before </body> in your HTML template:
<!-- index.html (Vite) or public/index.html (Create React App) --><body> <div id="root"></div> <script async defer src="https://api.askvault.co/widget.js" data-token="wt_yourtoken_here" ></script></body>Reload the page and the launcher appears in the bottom-right corner. The script is about 73 KB compressed and loads without blocking your page.
Option 2: next/script (Next.js)
Use the built-in Script component.
In the App Router, load the widget once from your root layout:
import Script from 'next/script';
export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> {children} <Script src="https://api.askvault.co/widget.js" strategy="afterInteractive" data-token="wt_yourtoken_here" /> </body> </html> );}In the Pages Router, put the same <Script> in pages/_app.tsx. Use afterInteractive; the widget never needs to run before hydration.
Option 3: a client component (load it only on some routes)
This loads the widget on chosen pages only.
To show the widget only on a help page or only to signed-in users, inject the script from an effect:
'use client';import { useEffect } from 'react';
export function AskVaultWidget({ token }: { token: string }) { useEffect(() => { if (document.getElementById('askvault-widget-script')) return; const s = document.createElement('script'); s.id = 'askvault-widget-script'; s.src = 'https://api.askvault.co/widget.js'; s.async = true; s.setAttribute('data-token', token); document.body.appendChild(s); }, [token]); return null;}Render <AskVaultWidget token={process.env.NEXT_PUBLIC_ASKVAULT_TOKEN!} /> on the pages that should have it. Once loaded, the widget stays for the rest of the browser session, so put it on the routes where it should first appear.
Tell the widget who the user is
Growth+Passing the signed-in user lets the agent greet them by name and use their plan or account details in answers. The widget loads asynchronously, so set up its call queue first; queued calls run as soon as it loads:
useEffect(() => { window.AskVault = window.AskVault || { _pending: [], setUser(d) { this._pending.push({ type: 'setUser', data: d }); } }; window.AskVault.setUser({ user_id: currentUser.id, email: currentUser.email, name: currentUser.name, // HMAC-SHA256 of user_id with your identity verification secret, computed on your server hash: currentUser.askvaultHash, });}, [currentUser]);Always send the hash. It is made on your server with the secret from Channels > Website Widget > Identity verification, and it stops anyone in the browser from posing as another user. Call window.AskVault.clearUser() when the user signs out. Details: identity verification.
Track events for proactive messages
window.AskVault.track('event_name') records an event your proactive rules can react to, for example opening the chat after a failed checkout or on a pricing page. See automation rules.
Controlling the widget from code Rolling out: open(), close(), toggle(), sendMessage() and event listeners, so a "Chat with us" button in your app can open the panel directly.
TypeScript types
Add this declaration file for autocomplete.
declare global { interface Window { AskVault?: { setUser: (user: { user_id: string; email?: string; name?: string; plan?: string; attributes?: Record<string, unknown>; hash?: string; }) => void; clearUser: () => void; track: (name: string, props?: Record<string, unknown>) => void; }; }}export {};Check that it works
- The launcher shows on the routes you expect. If it is missing on one, that route is not loading the script.
- Answers cite your content. Ask something only your docs can answer and check the sources under the reply. No sources usually means your pages are not indexed yet; check the Knowledge.
- Conversations reach the dashboard. Send a test message and look for it under Inbox > Chat History.
Troubleshooting
- The launcher never appears. Look for
widget.jsin the browser's Network tab. If it loads, check that the token is correct and the workspace is active. - The widget refuses to answer on your production domain. Add the domain to the widget token's allowed origins in Channels > Web widget.
- "window is not defined" during server rendering. Only touch
window.AskVaultinsideuseEffector behind atypeof window !== 'undefined'check. - Two chat bubbles overlap. Remove the other chat tool or move the AskVault launcher to the left in AI Agent > Appearance.
FAQ
Does it work with React Server Components?
Yes. The layout can stay a Server Component; the widget runs in the browser after hydration.
Will it increase my bundle size?
No. The browser loads it separately from your app's JavaScript.
Can I run two widgets on one page?
No. Use separate workspaces for, say, a sales bot and a support bot, and load whichever token fits the route. Growth includes 5 workspaces and Business 15 workspaces.
Related guides
- Add the widget to any website
- Add a chatbot to WordPress
- Identity verification
- Restrict the bot to specific URLs