The short answer
To embed a chatbot in a React app, load the widget script once from a small component mounted at the root of your app, and start the bot in the script's onload callback. Mounting it outside your routes keeps the same conversation open while users navigate, and passing the signed-in user at load time lets the bot greet them by name.
The widget is a self-contained web component, so React does not render or re-render it: your component's only job is to load it once at the right time. That keeps the integration small, safe in React 18 and 19 Strict Mode, and easy to test.
Step 1: A Loader Component
Create one component that adds the script if it is not already on the page and starts the bot when it loads:
// ConferbotWidget.jsx
import { useEffect } from 'react';
const SRC = 'https://cdn.conferbot.com/dist/v1/widget.min.js';
export default function ConferbotWidget({ botId, embedType = 'live_chat', user }) {
useEffect(() => {
// Load the script once, even if the component re-renders or remounts
if (document.getElementById('conferbot-js')) return;
const script = document.createElement('script');
script.id = 'conferbot-js';
script.src = SRC;
script.async = true;
script.onload = () => {
window.ConferbotWidget(botId, embedType, user ? { user } : undefined);
};
document.body.appendChild(script);
}, [botId, embedType, user]);
return null; // the widget renders itself as <conferbot-widget>
}The id check is what makes it safe in Strict Mode, which runs effects twice in development, and when the component remounts. Choose the embed type to match where the chat should appear:
| Embed type | What it shows |
|---|---|
live_chat | A launcher bubble that opens the chat window (the usual choice) |
popup_chat | The chat opens as a popup |
fullpage_chat | The chat fills the page, for a dedicated chat or support page |
Step 2: Mount It Once, at the Root
Render the loader outside your router's routes so it is mounted once and never unmounts on navigation:
// App.jsx - mount once, at the root, so it survives route changes
import ConferbotWidget from './ConferbotWidget';
export default function App() {
return (
<>
<Routes />
<ConferbotWidget botId={import.meta.env.VITE_CONFERBOT_BOT_ID} />
</>
);
}Once loaded, the widget stays on the page across client-side route changes, so the conversation is not lost. To keep it off certain pages, such as checkout or an admin area, use the page rules in the Conferbot dashboard (hide on specific URLs, optionally including their sub-pages) rather than unmounting the component. The same settings let you show it on desktop only, phones only, or both.
Step 3: Pass the Signed-In User
If your app knows who the visitor is, pass it at load time so the bot and your agents see a name and email instead of an anonymous visitor:
<ConferbotWidget
botId={BOT_ID}
user={currentUser ? {
id: currentUser.id,
name: currentUser.name,
email: currentUser.email,
} : undefined}
/>Because the identity is read when the widget starts, render the loader after your auth state has resolved (for example inside your authenticated layout) if you want logged-in details on the first message. You can also pass customData in the same options object for values such as plan or account ID.
The widget's public interface is deliberately small: one call, ConferbotWidget(botId, embedType, options), where options can carry user (id, name, email and other fields) and customData. Appearance, greeting, which pages it appears on and whether it shows on desktop, phones or both are set in the Conferbot dashboard, not in code. There are no JavaScript methods to open, close or send messages programmatically, so build your integration around loading it once and passing identity at load time.
Styling the Widget
Colours, launcher icon, position, corner radius, fonts and greeting are set in the Conferbot dashboard and apply everywhere the bot runs, so you do not need styled-components, Tailwind or CSS modules for the widget itself. Avoid overriding its internal CSS from your app: internal class names are not a stable API and can change between releases. To try designs before you build, the free chat widget designer previews colours and launchers in a working window.
Next.js, Remix and Server-Side Rendering
The widget needs window and document, so it must only load in the browser. A useEffect never runs on the server, so the loader component above is already SSR-safe in Remix, Gatsby and plain React with SSR. In Next.js, the built-in Script component is the simplest option:
// app/layout.jsx (App Router) or pages/_app.jsx
import Script from 'next/script';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://cdn.conferbot.com/dist/v1/widget.min.js"
strategy="afterInteractive"
onLoad={() => window.ConferbotWidget(process.env.NEXT_PUBLIC_CONFERBOT_BOT_ID, 'live_chat')}
/>
</body>
</html>
);
}See our dedicated Next.js chatbot guide for App Router, Pages Router and loading-strategy details.
Performance
- Load asynchronously. The loader sets
asyncand runs after your app renders, so it does not block your first paint. - Load it later if you prefer. To defer it further, start the loader in
requestIdleCallbackor after the first user interaction; the chat bubble then appears a moment later. - Do not bundle it. Loading from the CDN keeps the widget out of your JavaScript bundle and lets browsers cache it across pages.
- Measure. Run Lighthouse or PageSpeed Insights before and after adding it to confirm there is no regression on your pages.
Testing and Debugging
In unit tests, mock window.ConferbotWidget and assert that your loader adds the script once and starts the bot with the right arguments:
// ConferbotWidget.test.jsx (Jest or Vitest + React Testing Library)
import { render } from '@testing-library/react';
import ConferbotWidget from './ConferbotWidget';
test('loads the widget script once and starts the bot', () => {
window.ConferbotWidget = jest.fn();
render(<ConferbotWidget botId="test-bot" />);
const script = document.getElementById('conferbot-js');
expect(script.src).toContain('widget.min.js');
script.onload();
expect(window.ConferbotWidget).toHaveBeenCalledWith('test-bot', 'live_chat', undefined);
});In end-to-end tests (Playwright or Cypress), wait for the conferbot-widget element to appear on the page. If the bubble does not show in a real browser, check the console for a blocked script (a Content Security Policy must allow cdn.conferbot.com), confirm the bot ID, and check that the current URL is not in the dashboard's hidden pages list.
Production Checklist
Before deploying your chatbot-integrated React application to production, verify each item on this checklist.
Performance
- Chatbot script loads asynchronously (async script tag)
- The widget loads from the CDN, not your bundle
- Core Web Vitals show no regression (run Lighthouse before/after)
- Chatbot initializes after first meaningful paint, not during critical path
- Content Security Policy allows cdn.conferbot.com
Functionality
- Widget appears on correct routes and is hidden on excluded routes
- Conversation persists across route navigation (widget does not remount)
- User context is passed correctly (test with authenticated and anonymous users)
- Hidden pages and device rules are set in the dashboard
- The loader is mounted once, outside the routes
- Works in React Strict Mode without duplicate widgets
Styling
- Widget matches brand colors, typography, and border radius
- Dark mode and light mode both work correctly
- Widget does not overlap with navigation, CTAs, or critical content
- Mobile responsiveness is verified on actual devices (not just browser simulation)
- Z-index is high enough to float above all page elements including modals
SSR and Build
- No SSR hydration errors in browser console
- Dynamic import or mounted-state pattern is in place for SSR frameworks
- Production build completes without chatbot-related warnings
- Source maps do not expose chatbot configuration or API keys
Accessibility
- Chat trigger button has proper aria-label
- Chat window is keyboard navigable (Tab, Enter, Escape to close)
- Screen readers announce new messages
- Focus management is correct (focus moves to chat on open, returns to trigger on close)
Security
- Chatbot script loaded from HTTPS CDN only
- No sensitive data (API keys, user tokens) exposed in client-side chatbot configuration
- Content Security Policy (CSP) headers allow chatbot script and WebSocket domains
- User input is sanitized by the chatbot platform (verify XSS protection)
This checklist ensures your chatbot integration is production-ready across performance, functionality, styling, SSR, accessibility, and security dimensions. For ongoing monitoring, track the chatbot's impact on your Core Web Vitals scores monthly and review conversation analytics weekly during the first month post-launch. For a comprehensive chatbot performance monitoring framework, see our chatbot analytics and metrics guide.
Was this article helpful?
Build and deploy in 10 minutes. No coding needed.
How to Embed a Chatbot in a React App FAQ
Everything you need to know about chatbots for how to embed a chatbot in a react app.
About the Author
The Conferbot team writes about building, deploying, and improving AI chatbots.
View all articles