React
Add an AI Chatbot to Your React App
Load the SDK from the CDN, mount it in an effect, and unmount it in the cleanup. A drop-in component, with the StrictMode double-mount handled.
The component
Copy this into your project, replace the key, and render it once in your app shell.
import { useEffect, useRef } from "react";
const CDN = "https://cdn.cortexvia.in";
const CHATBOT_KEY = "ctx-cb-xxxxxxxxxxxxxxxx";
/* One shared promise, so the CDN files are requested once per page even if the
* component mounts twice (React StrictMode does exactly that in development). */
let sdkReady: Promise<unknown> | null = null;
function loadSdk() {
if (sdkReady) return sdkReady;
sdkReady = new Promise((resolve, reject) => {
if ((window as any).ChatbotSDK) {
resolve((window as any).ChatbotSDK);
return;
}
const css = document.createElement("link");
css.rel = "stylesheet";
css.href = CDN + "/chatbotsdk-web.css";
document.head.appendChild(css);
const script = document.createElement("script");
script.src = CDN + "/chatbotsdk-web.iife.js";
script.async = true;
script.onload = () => resolve((window as any).ChatbotSDK);
script.onerror = () => reject(new Error("Cortexvia SDK failed to load"));
document.head.appendChild(script);
});
return sdkReady;
}
export default function CortexviaChat() {
const mountNode = useRef<HTMLDivElement>(null);
useEffect(() => {
let cancelled = false;
let handle: { unmount: () => void } | null = null;
loadSdk().then((sdk: any) => {
// The effect can be torn down before the CDN responds.
if (cancelled || !mountNode.current || !sdk?.mount) return;
handle = sdk.mount(mountNode.current, {
chatbotKey: CHATBOT_KEY,
config: { mode: "chatbot", position: "bottom-right" },
});
});
return () => {
cancelled = true;
handle?.unmount();
};
}, []);
return <div ref={mountNode} />;
}The shared sdkReady promise keeps the CDN files to one request per page even when the component mounts twice, and the cleanup is what makes StrictMode safe.
- Render it in a layout that survives navigation, not inside a route
- The cancelled flag covers unmounting while the CDN request is in flight
- Drop the TypeScript annotations if your project is plain JavaScript
Get these right
Everything that separates a clean integration from two launchers and a leak.
Load the SDK once
The CDN files should be requested once per page, not once per component instance. A module-scope promise gives every mount the same in-flight request.
Mount in an effect, unmount in the cleanup
mount returns a handle with unmount and update. Calling unmount in the effect's cleanup is what keeps a route change from leaving a widget behind.
StrictMode mounts effects twice
In development React 18 and 19 run mount, cleanup, mount. Without the cleanup that shows up as two launchers, and it will look like a Cortexvia bug.
Guard the async gap
The component can unmount while the CDN request is still in flight, so a cancelled flag prevents mounting into a node React has already discarded.
Keeping the handle
If you would rather control the widget from outside React, store the handle globally.
<script>
window.cvChatHandle = window.ChatbotSDK.mount("#cv-chat", {
chatbotKey: "ctx-cb-xxxxxxxxxxxxxxxx",
config: {
mode: "chatbot"
}
});
</script>The handle exposes update and unmount. update takes the same shape as mount, so you can switch mode, placeholder, or styling without remounting.
- update reconfigures in place; remounting is not required
- unmount removes the widget and its DOM
- Null the reference after unmount so a stale handle is never reused
Why Cortexvia
A CDN widget with local currency pricing, with the trust pages published up front.
No npm dependency
The widget ships from the CDN and attaches to window.ChatbotSDK, so there is no package to add to your bundle and no version to keep in step with React.
Out of your critical path
Because the loader is injected after mount, the widget never blocks hydration or competes with your app's first meaningful paint.
Styles stay isolated
The widget renders inside the container you hand it, so it does not inherit your CSS-in-JS or utility classes and your styles do not leak into it.
Local pricing, free tier to test
Plans are priced in your local currency, and the free tier is enough to wire it into a real app.
Questions
What developers ask before wiring the widget into an app.
More platforms
The same SDK works anywhere a script tag can go. Pick your platform for the exact steps.
Get started
Start on the free tier with a document or two, copy the component, and render it in your app shell.
Mount in an effect, unmount in the cleanup. Get that pair right and the rest of the integration is a key and a config object.
