Nexoply

How-to guides

How to add an AI chatbot to a React app (Vite, CRA, SPA)

Add an AI chatbot to a React app built with Vite, Create React App or any SPA: one script tag in index.html or a small useEffect. No npm package needed.

Updated September 30, 2026 5 min read

In short

Paste the one-line Nexoply script into your app's index.html, just before </body>, and the chat button appears on every route. If you'd rather load it from code (for example after cookie consent, or with the key in an env var), add the script from a useEffect in your root component. There is no npm package to install and no AI key to manage: the widget is a single async script.

  • Simplest: one script tag in index.html before </body> (Vite: index.html at the project root; Create React App: public/index.html).
  • From code: a useEffect in your root component that appends the script once. No cleanup needed, and StrictMode's double run is harmless.
  • It mounts on document.body inside a Shadow DOM, outside your React root, so re-renders, route changes and your CSS never affect it.
  • Open the chat from your own button with window.dispatchEvent(new Event("nexoply:open-chat")).
  • Testing on localhost? Add your dev host with its port to Allowed websites, or leave that list empty.

Before you start

You need two things before touching your code:

  • A Nexoply account and your chatbot key. Sign up free (the Free plan includes 100 conversations a month, no card needed), then open Chatbot in the dashboard. The install code there already contains your key. The key is public: it isn't a secret and gives no access to the account, so it's fine in client-side code.
  • The business information the assistant answers from: services and prices, FAQs, policies, opening hours and service areas. The quickest way is to enter the website address and let Nexoply import the public pages, including pages built with JavaScript, so a React site works too. Nothing is added until it's approved.

Every React SPA has an HTML shell. In Vite it's index.html at the project root; in Create React App it's public/index.html. Paste the code just before the closing </body> tag:

<body>
  <div id="root"></div>
  <script type="module" src="/src/main.tsx"></script>

  <!-- Nexoply AI chatbot -->
  <script src="https://nexoply.com/widget.js" data-chatbot="YOUR-KEY" async></script>
</body>

Replace YOUR-KEY with the key from the Chatbot page. The script is loaded asynchronously, so it never blocks your app from rendering. Add data-open="true" to the tag if you want the chat window to open on load. That's the whole install: rebuild, deploy, and the button shows on every route.

Option 2: Load it from a component with useEffect

Load it from code when you want control over when it appears, for example only after a visitor accepts your cookie banner, or to keep the key in an environment variable per deployment. In Vite, put VITE_NEXOPLY_KEY=your-key in a .env file and add this to your root component:

// src/App.tsx
import { useEffect } from "react";

const NEXOPLY_KEY: string = import.meta.env.VITE_NEXOPLY_KEY;

export default function App() {
  useEffect(() => {
    // Already on the page? Nothing to do.
    if (document.querySelector("script[data-chatbot]")) return;
    const s = document.createElement("script");
    s.src = "https://nexoply.com/widget.js";
    s.async = true;
    s.dataset.chatbot = NEXOPLY_KEY;
    document.body.appendChild(s);
  }, []);

  return <main>{/* your app */}</main>;
}

The widget reads its settings from its own script tag, so the tag must keep the data-chatbot attribute and a src containing widget.js. Setting s.dataset.chatbot creates that attribute for you. Create React App doesn't have import.meta.env: use process.env.REACT_APP_NEXOPLY_KEY there instead.

  • Why there's no cleanup function. The widget has no API to remove it once loaded, and it lives outside your React tree, so unmounting App doesn't need to undo anything. Keep this effect in the root component, which stays mounted for the whole visit.
  • Why StrictMode is fine. In development, React StrictMode runs effects twice. The querySelector check skips the second run, and even if the script were added twice, the widget mounts only once per chatbot key. Hot reload is safe for the same reason.

How it behaves in a single-page app

  • It stays out of your React tree. The widget mounts on document.body, outside your #root element, so re-renders and route changes never remove or restyle it.
  • Shadow DOM both ways. Your CSS (Tailwind, CSS modules, global resets) doesn't leak into the chat, and its CSS doesn't leak into your app.
  • Route changes just work. With React Router or any history-based router, visitors keep their conversation while they navigate, and page rules (such as a side tab only on /pricing) are re-checked when the URL changes. No extra code.
  • No cookies. It stores an anonymous visitor id in localStorage and the current conversation in sessionStorage.

Open the chat from your own button

Want a "Chat with us" button in your hero section or pricing page? Dispatch the nexoply:open-chat event on window:

export function ChatButton() {
  return (
    <button onClick={() => window.dispatchEvent(new Event("nexoply:open-chat"))}>
      Chat with us
    </button>
  );
}

It needs no import: the widget listens for this event on window. If you'd rather show only your own button, set the message button to "No button" with a page rule in the dashboard.

Local testing and troubleshooting

Before you deploy, check these three settings:

  • Allowed websites. If you filled in Only allow on these websites (dashboard → Chatbot), add your dev host with its port, such as localhost:5173 for Vite or localhost:3000 for Create React App, or leave the list empty while testing.
  • Content-Security-Policy. If your app sends a CSP header or meta tag, allow https://nexoply.com in script-src, connect-src and img-src.
  • Check it end to end. Run the app, click the chat button, ask a real question and look for it under Conversations in the dashboard.

If the button still doesn't appear:

  • Empty key. Log NEXOPLY_KEY. Vite only exposes variables that start with VITE_, and you need to restart the dev server after editing .env.
  • Blocked request. Look at the browser console and Network tab for CSP errors or a blocked widget.js.
  • Chatbot switched off. Check that the Chatbot enabled switch is on.
  • Ad blockers. Rarely, a browser extension blocks chat widgets. Try a private window without extensions.

Next steps: customize it without redeploying

Once the script is in, you won't need to touch the code again: changes made in the dashboard reach the site within about a minute. Under Chatbot → Customize you can style the message button (icon or your own image or SVG, text label, shape, size, colours, position) or use a side tab on the edge of the screen instead. Set a different look on phones, and use page rules to change it on chosen routes (for example /pricing, or /services/*). The chat window takes your fonts, colours and logo, and the start screen can offer buttons like Book, Call or WhatsApp.

Using another framework? See the Next.js guide or the Vue guide. For the full picture, read how it works or compare plans.

Frequently asked questions

Is there an npm package or React component?

No, and you don't need one. The widget is always the script https://nexoply.com/widget.js with a data-chatbot attribute. Add it in index.html or from a useEffect; all settings live in the dashboard.

Does it work with React Router?

Yes. The widget keeps the conversation while visitors navigate client-side and re-checks page rules when the URL changes, with no extra code. It lives outside your React tree, so route changes never remove it.

Will it slow down my app?

No noticeable effect. The script loads asynchronously and never blocks rendering, and it runs inside its own Shadow DOM without touching your components.

Does it work with Next.js?

Yes, with the next/script component in your root layout. See the Next.js guide for the App Router and Pages Router snippets.

How do I hide it on some routes?

There's no API to remove it once loaded. Instead, add a page rule with "No button" (dashboard → Chatbot → Customize → Message button → Different on some pages), or turn the chatbot off.

Let your website answer the questions for you

Add your business info, test your assistant, and put it on your website - all on the free plan.